react-lite-rich-text-editor 1.1.8 → 1.1.10

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-bg-color-swatch-none{background-color:#fff;background-image:linear-gradient(45deg,#d1d5db 25%,transparent 0,transparent 75%,#d1d5db 0),linear-gradient(45deg,#d1d5db 25%,transparent 0,transparent 75%,#d1d5db 0);background-position:0 0,4px 4px;background-size:8px 8px}.rte-bg-color-clear{position:relative}.rte-bg-color-clear-icon{align-items:center;display:inline-flex;font-size:11px;font-weight:700;height:14px;justify-content:center;line-height:1;position:relative;width:14px}.rte-bg-color-clear-icon:after{background:currentColor;border-radius:1px;content:\"\";height:2px;left:-1px;position:absolute;right:-1px;top:50%;transform:rotate(-35deg)}.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;max-width:100%}.rte-area-highlight-block>blockquote,.rte-area-highlight-block>div,.rte-area-highlight-block>h1,.rte-area-highlight-block>h2,.rte-area-highlight-block>h3,.rte-area-highlight-block>h4,.rte-area-highlight-block>h5,.rte-area-highlight-block>h6,.rte-area-highlight-block>p{margin-bottom:0;margin-top:0}.rte-area-highlight-block>*+*{margin-top:.25em}.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,220 @@ const escapeAttr = str => escapeHtml(str).replace(/"/g, """);
421
421
 
422
422
  // URL detection regex
423
423
  const URL_REGEX = /(https?:\/\/[^\s]+)/g;
424
+ const TRANSPARENT_BACKGROUNDS = new Set(["", "transparent", "initial", "inherit", "rgba(0, 0, 0, 0)", "rgb(0, 0, 0, 0)"]);
425
+ const isTransparentBackground = value => {
426
+ if (!value) return true;
427
+ const normalized = value.trim().toLowerCase();
428
+ if (TRANSPARENT_BACKGROUNDS.has(normalized)) return true;
429
+ const match = normalized.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)$/);
430
+ if ((match === null || match === void 0 ? void 0 : match[4]) !== undefined && parseFloat(match[4]) === 0) return true;
431
+ return false;
432
+ };
433
+ const unwrapNode = element => {
434
+ const parent = element.parentNode;
435
+ if (!parent) return;
436
+ while (element.firstChild) {
437
+ parent.insertBefore(element.firstChild, element);
438
+ }
439
+ parent.removeChild(element);
440
+ };
441
+ const unwrapAreaHighlightBlock = block => {
442
+ var _block$classList;
443
+ if (!(block !== null && block !== void 0 && (_block$classList = block.classList) !== null && _block$classList !== void 0 && _block$classList.contains("rte-area-highlight-block"))) return;
444
+ const parent = block.parentNode;
445
+ if (!parent) return;
446
+ const fragment = document.createDocumentFragment();
447
+ while (block.firstChild) {
448
+ fragment.appendChild(block.firstChild);
449
+ }
450
+ parent.insertBefore(fragment, block);
451
+ parent.removeChild(block);
452
+ };
453
+ const stripInlineBackground = element => {
454
+ if (!(element instanceof HTMLElement)) return;
455
+ if (element.classList.contains("rte-block-highlight")) {
456
+ element.classList.remove("rte-block-highlight");
457
+ }
458
+ if (element.tagName === "MARK") {
459
+ unwrapNode(element);
460
+ return;
461
+ }
462
+ const hadBackground = element.style.backgroundColor && !isTransparentBackground(element.style.backgroundColor) || element.style.background && !isTransparentBackground(element.style.background);
463
+ if (!hadBackground) return;
464
+ element.style.backgroundColor = "";
465
+ element.style.background = "";
466
+ if (!element.getAttribute("style")) {
467
+ element.removeAttribute("style");
468
+ }
469
+ const tag = element.tagName;
470
+ if ((tag === "SPAN" || tag === "FONT") && !element.className && element.attributes.length === 0) {
471
+ unwrapNode(element);
472
+ }
473
+ };
474
+ const collectElementsInRange = (range, root) => {
475
+ const elements = [];
476
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
477
+ let node = walker.currentNode;
478
+ while (node) {
479
+ if (range.intersectsNode(node)) {
480
+ elements.push(node);
481
+ }
482
+ node = walker.nextNode();
483
+ }
484
+ return elements;
485
+ };
486
+ const elementHasRemovableBackground = element => {
487
+ var _element$style, _element$style2;
488
+ if (!(element instanceof HTMLElement)) return false;
489
+ if (element.classList.contains("rte-area-highlight-block")) return true;
490
+ if (element.classList.contains("rte-block-highlight")) return true;
491
+ if (element.tagName === "MARK") return true;
492
+ if ((_element$style = element.style) !== null && _element$style !== void 0 && _element$style.backgroundColor && !isTransparentBackground(element.style.backgroundColor)) {
493
+ return true;
494
+ }
495
+ if ((_element$style2 = element.style) !== null && _element$style2 !== void 0 && _element$style2.background && !isTransparentBackground(element.style.background)) {
496
+ return true;
497
+ }
498
+ return false;
499
+ };
500
+ const rectsIntersect = (a, b) => !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom);
501
+ const caretRangeFromClientPoint = (x, y) => {
502
+ var _document$caretPositi, _document;
503
+ if (document.caretRangeFromPoint) {
504
+ return document.caretRangeFromPoint(x, y);
505
+ }
506
+ const position = (_document$caretPositi = (_document = document).caretPositionFromPoint) === null || _document$caretPositi === void 0 ? void 0 : _document$caretPositi.call(_document, x, y);
507
+ if (!position) return null;
508
+ const range = document.createRange();
509
+ range.setStart(position.offsetNode, position.offset);
510
+ range.collapse(true);
511
+ return range;
512
+ };
513
+ const clipRectToBounds = (rect, bounds) => {
514
+ const left = Math.max(rect.left, bounds.left);
515
+ const top = Math.max(rect.top, bounds.top);
516
+ const right = Math.min(rect.right, bounds.right);
517
+ const bottom = Math.min(rect.bottom, bounds.bottom);
518
+ return {
519
+ left,
520
+ top,
521
+ right,
522
+ bottom,
523
+ width: Math.max(0, right - left),
524
+ height: Math.max(0, bottom - top)
525
+ };
526
+ };
527
+ 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)";
528
+ const getIntersectingBlocks = (editor, clientRect) => {
529
+ const candidates = [...editor.querySelectorAll(BLOCK_SELECTOR)].filter(el => {
530
+ if (el === editor || !editor.contains(el)) return false;
531
+ if (el.closest(".rte-area-highlight-block, .image-container, .video-container")) {
532
+ return false;
533
+ }
534
+ return rectsIntersect(el.getBoundingClientRect(), clientRect);
535
+ });
536
+ return candidates.filter(el => !candidates.some(other => other !== el && other.contains(el)));
537
+ };
538
+ const isHighlightableBlock = block => {
539
+ const text = (block.textContent || "").replace(/[\u00A0\u200B\s]/g, "");
540
+ if (text.length > 0) return true;
541
+ return !!block.querySelector("img, table, iframe, .image-container, .video-container, svg");
542
+ };
543
+ const createAreaHighlightWrapper = (color, textAlign, clipped, editorRect) => {
544
+ const widthPx = Math.max(40, Math.round(clipped.width));
545
+ const heightPx = Math.max(24, Math.round(clipped.height));
546
+ const region = document.createElement("div");
547
+ region.className = "rte-area-highlight-block";
548
+ region.style.backgroundColor = color;
549
+ region.style.display = "block";
550
+ region.style.width = `${widthPx}px`;
551
+ region.style.minHeight = `${heightPx}px`;
552
+ region.style.maxWidth = "100%";
553
+ region.style.padding = "6px 10px";
554
+ region.style.borderRadius = "4px";
555
+ region.style.margin = "4px 0";
556
+ region.style.boxSizing = "border-box";
557
+ if (textAlign === "center") {
558
+ region.style.marginLeft = "auto";
559
+ region.style.marginRight = "auto";
560
+ } else if (textAlign === "right" || textAlign === "end") {
561
+ region.style.marginLeft = "auto";
562
+ region.style.marginRight = "0";
563
+ } else {
564
+ const leftOffset = Math.max(0, Math.round(clipped.left - editorRect.left));
565
+ region.style.marginLeft = `${leftOffset}px`;
566
+ region.style.marginRight = "auto";
567
+ }
568
+ return region;
569
+ };
570
+ const insertAreaHighlightRegion = (editor, clientRect, color) => {
571
+ var _alignNode;
572
+ const editorRect = editor.getBoundingClientRect();
573
+ const clipped = clipRectToBounds(clientRect, editorRect);
574
+ const blocks = getIntersectingBlocks(editor, clipped).filter(isHighlightableBlock);
575
+ if (blocks.length > 0) {
576
+ const first = blocks[0];
577
+ const textAlign = window.getComputedStyle(first).textAlign;
578
+ const region = createAreaHighlightWrapper(color, textAlign, clipped, editorRect);
579
+ let totalBlockHeight = 0;
580
+ blocks.forEach(block => {
581
+ totalBlockHeight += block.getBoundingClientRect().height;
582
+ });
583
+ const targetHeight = Math.max(24, Math.round(clipped.height));
584
+ const innerPadding = 12;
585
+ const extraVertical = Math.max(0, targetHeight - totalBlockHeight - innerPadding);
586
+ if (extraVertical > 0) {
587
+ const pad = Math.round(extraVertical / 2);
588
+ region.style.paddingTop = `${6 + pad}px`;
589
+ region.style.paddingBottom = `${6 + pad}px`;
590
+ }
591
+ if (first.parentNode) {
592
+ first.parentNode.insertBefore(region, first);
593
+ } else {
594
+ editor.appendChild(region);
595
+ }
596
+ blocks.forEach(block => {
597
+ region.appendChild(block);
598
+ });
599
+ return true;
600
+ }
601
+ const insertRange = caretRangeFromClientPoint(clipped.left + clipped.width / 2, clipped.top + Math.min(8, clipped.height / 2)) || caretRangeFromClientPoint(clipped.left + 4, clipped.top + 4);
602
+ let alignNode = insertRange === null || insertRange === void 0 ? void 0 : insertRange.startContainer;
603
+ if (((_alignNode = alignNode) === null || _alignNode === void 0 ? void 0 : _alignNode.nodeType) === 3) alignNode = alignNode.parentNode;
604
+ const textAlign = alignNode instanceof HTMLElement ? window.getComputedStyle(alignNode).textAlign : "left";
605
+ const region = createAreaHighlightWrapper(color, textAlign, clipped, editorRect);
606
+ region.innerHTML = "&nbsp;";
607
+ if (insertRange && editor.contains(insertRange.startContainer)) {
608
+ insertRange.collapse(true);
609
+ let node = insertRange.startContainer;
610
+ if (node.nodeType === 3) node = node.parentNode;
611
+ while (node && node !== editor && node.parentNode !== editor) {
612
+ node = node.parentNode;
613
+ }
614
+ if (node && node !== editor && editor.contains(node)) {
615
+ node.parentNode.insertBefore(region, node);
616
+ } else {
617
+ editor.appendChild(region);
618
+ }
619
+ } else {
620
+ editor.appendChild(region);
621
+ }
622
+ return true;
623
+ };
624
+ const getClientRectFromDrag = (startX, startY, currentX, currentY) => {
625
+ const left = Math.min(startX, currentX);
626
+ const top = Math.min(startY, currentY);
627
+ const right = Math.max(startX, currentX);
628
+ const bottom = Math.max(startY, currentY);
629
+ return {
630
+ left,
631
+ top,
632
+ right,
633
+ bottom,
634
+ width: right - left,
635
+ height: bottom - top
636
+ };
637
+ };
424
638
  function RichTextEditor({
425
639
  onChange,
426
640
  showEditButton,
@@ -439,8 +653,11 @@ function RichTextEditor({
439
653
  onImageUpload
440
654
  }) {
441
655
  const editorRef = useRef(null);
656
+ const contentWrapperRef = useRef(null);
442
657
  const fileInputRef = useRef(null);
443
658
  const scrollTopRef = useRef(0);
659
+ const areaDragRef = useRef(null);
660
+ const bgColorRef = useRef("#ffff00");
444
661
  const [html, setHtml] = useState("");
445
662
  const [linkModalOpen, setLinkModalOpen] = useState(false);
446
663
  const [linkUrl, setLinkUrl] = useState("");
@@ -481,11 +698,15 @@ function RichTextEditor({
481
698
  const [tableCols, setTableCols] = useState(3);
482
699
  const [selectionVersion, setSelectionVersion] = useState(0);
483
700
  const [selectedMedia, setSelectedMedia] = useState(null);
701
+ const [mediaWidthInput, setMediaWidthInput] = useState("100");
702
+ const [mediaWidthUnit, setMediaWidthUnit] = useState("%");
484
703
  const [metrics, setMetrics] = useState({
485
704
  words: 0,
486
705
  chars: 0
487
706
  });
488
707
  const [isEmpty, setIsEmpty] = useState(!value);
708
+ const [areaHighlightMode, setAreaHighlightMode] = useState(false);
709
+ const [marqueePreview, setMarqueePreview] = useState(null);
489
710
  const updateMetrics = useCallback(() => {
490
711
  if (!editorRef.current) return;
491
712
  // Calculate metrics immediately but outside of render path
@@ -522,6 +743,10 @@ function RichTextEditor({
522
743
  selectionRangeRef.current = sel.getRangeAt(0).cloneRange();
523
744
  }
524
745
  };
746
+ const handleKeyUp = () => {
747
+ saveSelection();
748
+ setSelectionVersion(v => v + 1);
749
+ };
525
750
  const handleZoomIn = () => {
526
751
  setZoomLevel(prevZoom => prevZoom + 0.1);
527
752
  };
@@ -732,6 +957,42 @@ function RichTextEditor({
732
957
  const computedColor = window.getComputedStyle(sel.anchorNode.nodeType === 3 ? sel.anchorNode.parentNode : sel.anchorNode).color;
733
958
  return rgbToHex(computedColor);
734
959
  };
960
+ const getBackgroundColorAtCursor = () => {
961
+ const sel = window.getSelection();
962
+ if (!sel || !sel.rangeCount || !editorRef.current) return null;
963
+ let node = sel.anchorNode;
964
+ if (node.nodeType === 3) node = node.parentNode;
965
+ while (node && node !== editorRef.current) {
966
+ if (node.nodeType === 1) {
967
+ var _node$classList, _node$classList2, _node$style;
968
+ if ((_node$classList = node.classList) !== null && _node$classList !== void 0 && _node$classList.contains("rte-area-highlight-block")) {
969
+ const bg = node.style.backgroundColor;
970
+ return bg && !isTransparentBackground(bg) ? rgbToHex(bg) : null;
971
+ }
972
+ if ((_node$classList2 = node.classList) !== null && _node$classList2 !== void 0 && _node$classList2.contains("rte-block-highlight")) {
973
+ const bg = node.style.backgroundColor;
974
+ return bg && !isTransparentBackground(bg) ? rgbToHex(bg) : null;
975
+ }
976
+ const inlineBg = (_node$style = node.style) === null || _node$style === void 0 ? void 0 : _node$style.backgroundColor;
977
+ if (inlineBg && !isTransparentBackground(inlineBg)) {
978
+ return rgbToHex(inlineBg);
979
+ }
980
+ if (node.tagName === "MARK") {
981
+ const markBg = window.getComputedStyle(node).backgroundColor;
982
+ return markBg && !isTransparentBackground(markBg) ? rgbToHex(markBg) : null;
983
+ }
984
+ }
985
+ node = node.parentNode;
986
+ }
987
+ return null;
988
+ };
989
+ const syncBackgroundColorState = () => {
990
+ const activeBg = getBackgroundColorAtCursor();
991
+ setHasActiveTextBackground(!!activeBg);
992
+ if (activeBg) {
993
+ setBgColor(activeBg);
994
+ }
995
+ };
735
996
  const stripEditorChrome = root => {
736
997
  root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handle").forEach(element => element.remove());
737
998
  root.querySelectorAll(".rte-media-selected").forEach(element => {
@@ -771,9 +1032,31 @@ function RichTextEditor({
771
1032
  }
772
1033
  return 100;
773
1034
  };
1035
+ const getMediaWidthPx = container => {
1036
+ if (!container) return 0;
1037
+ if (container.dataset.widthPx) {
1038
+ return Number(container.dataset.widthPx);
1039
+ }
1040
+ const width = container.style.width || "";
1041
+ if (width.endsWith("px")) {
1042
+ return parseInt(width, 10) || 0;
1043
+ }
1044
+ return Math.round(container.getBoundingClientRect().width);
1045
+ };
1046
+ const syncMediaWidthControls = container => {
1047
+ if (!container) return;
1048
+ if (container.dataset.widthPx) {
1049
+ setMediaWidthInput(String(getMediaWidthPx(container)));
1050
+ setMediaWidthUnit("px");
1051
+ return;
1052
+ }
1053
+ setMediaWidthInput(String(getMediaWidthPercent(container)));
1054
+ setMediaWidthUnit("%");
1055
+ };
774
1056
  const applyMediaWidthPercent = (container, percent) => {
775
1057
  if (!container) return;
776
- const clamped = Math.max(25, Math.min(100, Math.round(percent)));
1058
+ const clamped = Math.max(10, Math.min(100, Math.round(percent)));
1059
+ delete container.dataset.widthPx;
777
1060
  container.dataset.widthPercent = String(clamped);
778
1061
  container.style.width = `${clamped}%`;
779
1062
  container.style.maxWidth = "100%";
@@ -794,8 +1077,36 @@ function RichTextEditor({
794
1077
  img.style.objectFit = "";
795
1078
  }
796
1079
  };
1080
+ const applyMediaWidthPx = (container, px) => {
1081
+ if (!container) return;
1082
+ const clamped = Math.max(20, Math.min(2000, Math.round(px)));
1083
+ delete container.dataset.widthPercent;
1084
+ container.dataset.widthPx = String(clamped);
1085
+ container.style.width = `${clamped}px`;
1086
+ container.style.maxWidth = "100%";
1087
+ container.style.marginLeft = "";
1088
+ container.style.marginTop = "";
1089
+ if (container.classList.contains("video-container")) {
1090
+ container.style.height = "0";
1091
+ container.style.paddingBottom = "56.25%";
1092
+ return;
1093
+ }
1094
+ const frame = getImageMediaTarget(container);
1095
+ if (!frame) return;
1096
+ frame.style.width = "100%";
1097
+ frame.style.height = "";
1098
+ const img = frame.querySelector("img");
1099
+ if (img) {
1100
+ img.style.height = "auto";
1101
+ img.style.objectFit = "";
1102
+ }
1103
+ };
797
1104
  const normalizeMediaWidth = container => {
798
1105
  if (!container) return;
1106
+ if (container.dataset.widthPx) {
1107
+ applyMediaWidthPx(container, Number(container.dataset.widthPx));
1108
+ return;
1109
+ }
799
1110
  if (container.classList.contains("image-small")) {
800
1111
  container.classList.remove("image-small");
801
1112
  applyMediaWidthPercent(container, 50);
@@ -811,11 +1122,7 @@ function RichTextEditor({
811
1122
  return;
812
1123
  }
813
1124
  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
- }
1125
+ applyMediaWidthPx(container, parseFloat(width));
819
1126
  return;
820
1127
  }
821
1128
  if (container.classList.contains("video-container")) {
@@ -858,12 +1165,18 @@ function RichTextEditor({
858
1165
  const startWidth = container.getBoundingClientRect().width;
859
1166
  const onMouseMove = moveEvent => {
860
1167
  const nextWidth = Math.max(60, startWidth + (moveEvent.clientX - startX));
861
- const percent = Math.round(nextWidth / editorWidth * 100);
862
- applyMediaWidthPercent(container, percent);
1168
+ if (container.dataset.widthPx) {
1169
+ applyMediaWidthPx(container, nextWidth);
1170
+ } else {
1171
+ const percent = Math.round(nextWidth / editorWidth * 100);
1172
+ applyMediaWidthPercent(container, percent);
1173
+ }
863
1174
  };
864
1175
  const onMouseUp = () => {
865
1176
  document.removeEventListener("mousemove", onMouseMove);
866
1177
  document.removeEventListener("mouseup", onMouseUp);
1178
+ syncMediaWidthControls(container);
1179
+ setSelectionVersion(v => v + 1);
867
1180
  triggerChange();
868
1181
  };
869
1182
  document.addEventListener("mousemove", onMouseMove);
@@ -959,12 +1272,14 @@ function RichTextEditor({
959
1272
  setIsUnderline(isParentStyle(container, "U", "underline"));
960
1273
  const computedColor = window.getComputedStyle(container).color;
961
1274
  setFontColor(rgbToHex(computedColor));
1275
+ syncBackgroundColorState();
962
1276
  } else {
963
1277
  setIsBold(document.queryCommandState("bold"));
964
1278
  setIsItalic(document.queryCommandState("italic"));
965
1279
  setIsUnderline(document.queryCommandState("underline"));
966
1280
  const computedColor = window.getComputedStyle(container).color;
967
1281
  setFontColor(rgbToHex(computedColor));
1282
+ syncBackgroundColorState();
968
1283
  }
969
1284
 
970
1285
  // 3. Current Font Size
@@ -990,11 +1305,202 @@ function RichTextEditor({
990
1305
  focus();
991
1306
  };
992
1307
  const [fontColor, setFontColor] = useState("#000000");
1308
+ const [bgColor, setBgColor] = useState("#ffff00");
1309
+ const [hasActiveTextBackground, setHasActiveTextBackground] = useState(false);
1310
+ useEffect(() => {
1311
+ bgColorRef.current = bgColor;
1312
+ }, [bgColor]);
993
1313
  const getActiveTextColor = () => getColorAtCursor() || fontColor;
994
- const handleColorChange = color => {
1314
+ const restoreSavedSelection = () => {
1315
+ if (!selectionRangeRef.current) return;
1316
+ const sel = window.getSelection();
1317
+ if (!sel) return;
1318
+ sel.removeAllRanges();
1319
+ sel.addRange(selectionRangeRef.current);
1320
+ };
1321
+ const applyTextColor = color => {
995
1322
  setFontColor(color);
996
- exec("foreColor", color);
1323
+ focus();
1324
+ restoreSavedSelection();
1325
+ document.execCommand("styleWithCSS", false, true);
1326
+ document.execCommand("foreColor", false, color);
1327
+ document.execCommand("styleWithCSS", false, false);
1328
+ triggerChange();
1329
+ };
1330
+ const applyBackgroundColor = color => {
1331
+ setBgColor(color);
1332
+ setHasActiveTextBackground(true);
1333
+ focus();
1334
+ restoreSavedSelection();
1335
+ document.execCommand("styleWithCSS", false, true);
1336
+ if (!document.execCommand("hiliteColor", false, color)) {
1337
+ document.execCommand("backColor", false, color);
1338
+ }
1339
+ document.execCommand("styleWithCSS", false, false);
1340
+ triggerChange();
1341
+ };
1342
+ const clearMediaHighlightStyles = editor => {
1343
+ editor.querySelectorAll(".image-container, .video-container, td, th").forEach(element => {
1344
+ if (!editor.contains(element)) return;
1345
+ if (!element.style.backgroundColor && !element.style.padding && !element.style.borderRadius) {
1346
+ return;
1347
+ }
1348
+ element.style.backgroundColor = "";
1349
+ element.style.padding = "";
1350
+ element.style.borderRadius = "";
1351
+ if (!element.getAttribute("style")) {
1352
+ element.removeAttribute("style");
1353
+ }
1354
+ });
1355
+ };
1356
+ const removeBackgroundColor = () => {
1357
+ const editor = editorRef.current;
1358
+ if (!editor) return;
1359
+ focus();
1360
+ restoreSavedSelection();
1361
+ const sel = window.getSelection();
1362
+ const range = sel !== null && sel !== void 0 && sel.rangeCount ? sel.getRangeAt(0) : null;
1363
+ document.execCommand("styleWithCSS", false, true);
1364
+ document.execCommand("hiliteColor", false, "transparent");
1365
+ document.execCommand("backColor", false, "transparent");
1366
+ document.execCommand("styleWithCSS", false, false);
1367
+ const processElement = element => {
1368
+ var _element$classList, _element$classList2, _element$classList3;
1369
+ if (!editor.contains(element)) return;
1370
+ if ((_element$classList = element.classList) !== null && _element$classList !== void 0 && _element$classList.contains("rte-area-highlight-block")) {
1371
+ unwrapAreaHighlightBlock(element);
1372
+ return;
1373
+ }
1374
+ if ((_element$classList2 = element.classList) !== null && _element$classList2 !== void 0 && _element$classList2.contains("image-container") || (_element$classList3 = element.classList) !== null && _element$classList3 !== void 0 && _element$classList3.contains("video-container")) {
1375
+ element.style.backgroundColor = "";
1376
+ element.style.padding = "";
1377
+ element.style.borderRadius = "";
1378
+ if (!element.getAttribute("style")) {
1379
+ element.removeAttribute("style");
1380
+ }
1381
+ return;
1382
+ }
1383
+ stripInlineBackground(element);
1384
+ };
1385
+ if (range && editor.contains(range.commonAncestorContainer)) {
1386
+ const elements = collectElementsInRange(range, editor).filter(elementHasRemovableBackground).reverse();
1387
+ elements.forEach(processElement);
1388
+ if (range.collapsed) {
1389
+ let node = range.startContainer;
1390
+ if (node.nodeType === 3) node = node.parentNode;
1391
+ while (node && node !== editor) {
1392
+ if (elementHasRemovableBackground(node)) {
1393
+ processElement(node);
1394
+ break;
1395
+ }
1396
+ node = node.parentNode;
1397
+ }
1398
+ }
1399
+ } else {
1400
+ [...editor.querySelectorAll(".rte-area-highlight-block")].forEach(unwrapAreaHighlightBlock);
1401
+ [...editor.querySelectorAll("mark")].forEach(mark => unwrapNode(mark));
1402
+ [...editor.querySelectorAll("[style*='background']")].forEach(element => {
1403
+ if (editor.contains(element)) {
1404
+ stripInlineBackground(element);
1405
+ }
1406
+ });
1407
+ }
1408
+ clearMediaHighlightStyles(editor);
1409
+ editor.querySelectorAll("span[style], font[style]").forEach(element => {
1410
+ if (!editor.contains(element)) return;
1411
+ if (!element.style.backgroundColor && !element.style.background) {
1412
+ if (!element.getAttribute("style")) {
1413
+ unwrapNode(element);
1414
+ }
1415
+ }
1416
+ });
1417
+ setHasActiveTextBackground(false);
1418
+ triggerChange();
1419
+ focus();
997
1420
  };
1421
+ const applyMarqueeHighlight = useCallback((clientRect, color) => {
1422
+ const editor = editorRef.current;
1423
+ if (!editor || clientRect.width < 10 || clientRect.height < 10) {
1424
+ return;
1425
+ }
1426
+ editor.focus();
1427
+ const editorRect = editor.getBoundingClientRect();
1428
+ const clippedRect = clipRectToBounds(clientRect, editorRect);
1429
+ if (clippedRect.width < 10 || clippedRect.height < 10) {
1430
+ return;
1431
+ }
1432
+ insertAreaHighlightRegion(editor, clippedRect, color);
1433
+ setAreaHighlightMode(false);
1434
+ setMarqueePreview(null);
1435
+ areaDragRef.current = null;
1436
+ editor.querySelectorAll("td, th").forEach(cell => {
1437
+ if (!editor.contains(cell)) return;
1438
+ if (cell.closest(".rte-area-highlight-block")) return;
1439
+ if (rectsIntersect(cell.getBoundingClientRect(), clippedRect)) {
1440
+ cell.style.backgroundColor = color;
1441
+ }
1442
+ });
1443
+ editor.querySelectorAll(".image-container, .video-container").forEach(media => {
1444
+ if (!editor.contains(media)) return;
1445
+ if (rectsIntersect(media.getBoundingClientRect(), clippedRect)) {
1446
+ media.style.backgroundColor = color;
1447
+ media.style.padding = "4px";
1448
+ media.style.borderRadius = "4px";
1449
+ }
1450
+ });
1451
+ updateMetrics();
1452
+ triggerChange();
1453
+ focus();
1454
+ }, [triggerChange, updateMetrics]);
1455
+ const handleAreaSelectMouseDown = useCallback(e => {
1456
+ if (!areaHighlightMode || !editable || disabled) return;
1457
+ if (e.button !== 0) return;
1458
+ if (e.target.closest(".rte-toolbar, .rte-media-toolbar, .rte-modal-overlay")) return;
1459
+ e.preventDefault();
1460
+ e.stopPropagation();
1461
+ const wrapper = contentWrapperRef.current;
1462
+ if (!wrapper) return;
1463
+ const wrapperRect = wrapper.getBoundingClientRect();
1464
+ areaDragRef.current = {
1465
+ startX: e.clientX,
1466
+ startY: e.clientY,
1467
+ currentX: e.clientX,
1468
+ currentY: e.clientY,
1469
+ wrapperRect
1470
+ };
1471
+ const updatePreview = (clientX, clientY) => {
1472
+ const drag = areaDragRef.current;
1473
+ if (!drag) return;
1474
+ drag.currentX = clientX;
1475
+ drag.currentY = clientY;
1476
+ const rect = getClientRectFromDrag(drag.startX, drag.startY, drag.currentX, drag.currentY);
1477
+ setMarqueePreview({
1478
+ left: rect.left - drag.wrapperRect.left,
1479
+ top: rect.top - drag.wrapperRect.top,
1480
+ width: rect.width,
1481
+ height: rect.height
1482
+ });
1483
+ };
1484
+ const onMove = ev => {
1485
+ ev.preventDefault();
1486
+ updatePreview(ev.clientX, ev.clientY);
1487
+ };
1488
+ const onUp = ev => {
1489
+ document.removeEventListener("mousemove", onMove);
1490
+ document.removeEventListener("mouseup", onUp);
1491
+ const drag = areaDragRef.current;
1492
+ areaDragRef.current = null;
1493
+ setMarqueePreview(null);
1494
+ if (!drag) return;
1495
+ const clientRect = getClientRectFromDrag(drag.startX, drag.startY, ev.clientX, ev.clientY);
1496
+ requestAnimationFrame(() => {
1497
+ applyMarqueeHighlight(clientRect, bgColorRef.current);
1498
+ });
1499
+ };
1500
+ document.addEventListener("mousemove", onMove);
1501
+ document.addEventListener("mouseup", onUp);
1502
+ updatePreview(e.clientX, e.clientY);
1503
+ }, [areaHighlightMode, editable, disabled, applyMarqueeHighlight]);
998
1504
  const addLink = () => {
999
1505
  const sel = window.getSelection();
1000
1506
  if (!sel || sel.rangeCount === 0) return;
@@ -1248,9 +1754,25 @@ function RichTextEditor({
1248
1754
  if (frame && !frame.querySelector(".image-delete-button")) {
1249
1755
  frame.appendChild(createMediaDeleteButton("Remove image", "image-delete-button", () => {
1250
1756
  existingWrapper.remove();
1757
+ clearMediaSelection();
1251
1758
  triggerChange && triggerChange();
1252
1759
  }));
1253
1760
  }
1761
+ if (!existingWrapper.dataset.mediaEnhanced) {
1762
+ existingWrapper.dataset.mediaEnhanced = "true";
1763
+ existingWrapper.addEventListener("click", event => {
1764
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1765
+ event.preventDefault();
1766
+ event.stopPropagation();
1767
+ selectMediaContainer(existingWrapper);
1768
+ });
1769
+ img.addEventListener("dblclick", event => {
1770
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1771
+ event.preventDefault();
1772
+ event.stopPropagation();
1773
+ openImageModal(img.src);
1774
+ });
1775
+ }
1254
1776
  attachMediaResizeHandle(existingWrapper);
1255
1777
  normalizeMediaWidth(existingWrapper);
1256
1778
  updateMediaControlVisibility(existingWrapper);
@@ -1328,14 +1850,33 @@ function RichTextEditor({
1328
1850
  event.stopPropagation();
1329
1851
  selectMediaContainer(container);
1330
1852
  });
1853
+ container.style.cursor = editable ? "pointer" : "default";
1854
+ container.dataset.mediaEnhanced = "true";
1855
+ container.addEventListener("click", event => {
1856
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1857
+ event.preventDefault();
1858
+ event.stopPropagation();
1859
+ selectMediaContainer(container);
1860
+ });
1861
+ const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
1862
+ container.remove();
1863
+ clearMediaSelection();
1864
+ triggerChange();
1865
+ });
1331
1866
  frame.appendChild(img);
1867
+ frame.appendChild(deleteBtn);
1332
1868
  container.appendChild(frame);
1333
- applyMediaWidthPercent(container, 25);
1869
+ attachMediaResizeHandle(container);
1870
+ applyMediaWidthPercent(container, 50);
1334
1871
 
1335
1872
  // Insert at cursor position
1336
1873
  insertNodeAtCursor(container);
1337
1874
  requestAnimationFrame(() => {
1875
+ var _editorRef$current5;
1338
1876
  processExistingMedia(editorRef.current);
1877
+ selectMediaContainer(container);
1878
+ syncMediaWidthControls(container);
1879
+ (_editorRef$current5 = editorRef.current) === null || _editorRef$current5 === void 0 || _editorRef$current5.focus();
1339
1880
  triggerChange();
1340
1881
  });
1341
1882
  }
@@ -1503,6 +2044,28 @@ function RichTextEditor({
1503
2044
  selection.addRange(newRange);
1504
2045
  };
1505
2046
  const handleKeyDown = useCallback(e => {
2047
+ var _editorRef$current6;
2048
+ if (e.key === "Escape") {
2049
+ if (areaHighlightMode) {
2050
+ e.preventDefault();
2051
+ areaDragRef.current = null;
2052
+ setMarqueePreview(null);
2053
+ setAreaHighlightMode(false);
2054
+ return;
2055
+ }
2056
+ if (selectedMedia) {
2057
+ e.preventDefault();
2058
+ clearMediaSelection();
2059
+ return;
2060
+ }
2061
+ }
2062
+ 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)) {
2063
+ e.preventDefault();
2064
+ selectedMedia.remove();
2065
+ clearMediaSelection();
2066
+ triggerChange();
2067
+ return;
2068
+ }
1506
2069
  if (applyMarkdownShortcut(e)) return;
1507
2070
 
1508
2071
  // Handle Enter key
@@ -1576,7 +2139,7 @@ function RichTextEditor({
1576
2139
  return;
1577
2140
  }
1578
2141
  if (e.key === "Backspace") {
1579
- var _node$closest, _node, _editorRef$current5;
2142
+ var _node$closest, _node, _editorRef$current7;
1580
2143
  const selection = window.getSelection();
1581
2144
  if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
1582
2145
  const range = selection.getRangeAt(0);
@@ -1586,7 +2149,7 @@ function RichTextEditor({
1586
2149
  node = node.parentNode;
1587
2150
  }
1588
2151
  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;
2152
+ if (!listItem || !((_editorRef$current7 = editorRef.current) !== null && _editorRef$current7 !== void 0 && _editorRef$current7.contains(listItem))) return;
1590
2153
  const list = listItem.parentNode;
1591
2154
  if (isListItemEffectivelyEmpty(listItem)) {
1592
2155
  e.preventDefault();
@@ -1635,7 +2198,7 @@ function RichTextEditor({
1635
2198
  e.preventDefault();
1636
2199
  exec("underline");
1637
2200
  }
1638
- }, [exec, triggerChange, fontColor]);
2201
+ }, [exec, triggerChange, fontColor, areaHighlightMode, selectedMedia]);
1639
2202
  const confirmLink = () => {
1640
2203
  // Add protocol if missing
1641
2204
  let url = linkUrl.trim();
@@ -1677,7 +2240,7 @@ function RichTextEditor({
1677
2240
  setCurrentFontSize("16");
1678
2241
  setCurrentLineHeight("");
1679
2242
  setFontColor("#000000");
1680
- triggerChange();
2243
+ removeBackgroundColor();
1681
2244
  focus();
1682
2245
  };
1683
2246
  const deleteTextBeforeCursorInBlock = (block, range, selection) => {
@@ -1903,7 +2466,11 @@ function RichTextEditor({
1903
2466
  }
1904
2467
  }, [disabled]);
1905
2468
  const handleEditorClick = useCallback(e => {
1906
- var _editorRef$current6;
2469
+ var _editorRef$current8;
2470
+ if (areaHighlightMode) {
2471
+ e.preventDefault();
2472
+ return;
2473
+ }
1907
2474
  setSelectionVersion(v => v + 1);
1908
2475
  const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
1909
2476
  if (deleteBtn && editable && editorFocused) {
@@ -1918,7 +2485,10 @@ function RichTextEditor({
1918
2485
  return;
1919
2486
  }
1920
2487
  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) {
2488
+ if (clickedMedia && (_editorRef$current8 = editorRef.current) !== null && _editorRef$current8 !== void 0 && _editorRef$current8.contains(clickedMedia) && editable) {
2489
+ e.preventDefault();
2490
+ e.stopPropagation();
2491
+ selectMediaContainer(clickedMedia);
1922
2492
  return;
1923
2493
  }
1924
2494
 
@@ -1949,16 +2519,78 @@ function RichTextEditor({
1949
2519
  }
1950
2520
  }, 0);
1951
2521
  }
1952
- }, [editable, disabled, editorFocused, triggerChange]);
2522
+ }, [areaHighlightMode, editable, disabled, editorFocused, triggerChange]);
2523
+ useEffect(() => {
2524
+ if (!selectedMedia) return;
2525
+ syncMediaWidthControls(selectedMedia);
2526
+ }, [selectedMedia, selectionVersion]);
2527
+ useEffect(() => {
2528
+ const editor = editorRef.current;
2529
+ if (!editor || !selectedMedia) return;
2530
+ const bumpToolbarPosition = () => setSelectionVersion(v => v + 1);
2531
+ editor.addEventListener("scroll", bumpToolbarPosition, {
2532
+ passive: true
2533
+ });
2534
+ window.addEventListener("resize", bumpToolbarPosition, {
2535
+ passive: true
2536
+ });
2537
+ return () => {
2538
+ editor.removeEventListener("scroll", bumpToolbarPosition);
2539
+ window.removeEventListener("resize", bumpToolbarPosition);
2540
+ };
2541
+ }, [selectedMedia]);
1953
2542
  const renderMediaToolbar = () => {
1954
2543
  if (!selectedMedia || !editorRef.current || !editable) return null;
1955
- const editorRect = editorRef.current.getBoundingClientRect();
2544
+ const wrapperEl = contentWrapperRef.current;
2545
+ const anchorRect = (wrapperEl === null || wrapperEl === void 0 ? void 0 : wrapperEl.getBoundingClientRect()) ?? editorRef.current.getBoundingClientRect();
1956
2546
  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;
2547
+ const relTop = mediaRect.top - anchorRect.top;
2548
+ const relLeft = mediaRect.left - anchorRect.left;
2549
+ const mediaWidth = mediaRect.width;
2550
+ const mediaHeight = mediaRect.height;
1960
2551
  const currentPercent = getMediaWidthPercent(selectedMedia);
1961
2552
  const widthPresets = [25, 50, 75, 100];
2553
+ const toolbarWidth = 340;
2554
+ const toolbarHeight = 40;
2555
+ const gap = 8;
2556
+ const preferredTop = relTop - toolbarHeight - gap;
2557
+ const toolbarTop = preferredTop >= 4 ? preferredTop : relTop + mediaHeight + gap;
2558
+ const applyCustomMediaWidth = () => {
2559
+ const num = parseFloat(mediaWidthInput);
2560
+ if (isNaN(num) || num <= 0) {
2561
+ syncMediaWidthControls(selectedMedia);
2562
+ return;
2563
+ }
2564
+ if (mediaWidthUnit === "px") {
2565
+ applyMediaWidthPx(selectedMedia, num);
2566
+ } else {
2567
+ applyMediaWidthPercent(selectedMedia, num);
2568
+ }
2569
+ syncMediaWidthControls(selectedMedia);
2570
+ setSelectionVersion(v => v + 1);
2571
+ triggerChange();
2572
+ };
2573
+ const handleUnitChange = newUnit => {
2574
+ if (newUnit === mediaWidthUnit) return;
2575
+ const editorInnerWidth = getEditorInnerWidth();
2576
+ const currentVal = parseFloat(mediaWidthInput);
2577
+ if (isNaN(currentVal) || currentVal <= 0) {
2578
+ setMediaWidthUnit(newUnit);
2579
+ return;
2580
+ }
2581
+ let converted = currentVal;
2582
+ if (newUnit === "px" && mediaWidthUnit === "%") {
2583
+ converted = Math.round(editorInnerWidth * currentVal / 100);
2584
+ applyMediaWidthPx(selectedMedia, converted);
2585
+ } else if (newUnit === "%" && mediaWidthUnit === "px") {
2586
+ converted = Math.min(100, Math.max(10, Math.round(currentVal / editorInnerWidth * 100)));
2587
+ applyMediaWidthPercent(selectedMedia, converted);
2588
+ }
2589
+ setMediaWidthInput(String(converted));
2590
+ setMediaWidthUnit(newUnit);
2591
+ setSelectionVersion(v => v + 1);
2592
+ triggerChange();
2593
+ };
1962
2594
  const handleAlignment = align => {
1963
2595
  selectedMedia.classList.remove("image-align-left", "image-align-center", "image-align-right");
1964
2596
  selectedMedia.classList.add(`image-align-${align}`);
@@ -1968,6 +2600,8 @@ function RichTextEditor({
1968
2600
  };
1969
2601
  const setWidth = percent => {
1970
2602
  applyMediaWidthPercent(selectedMedia, percent);
2603
+ syncMediaWidthControls(selectedMedia);
2604
+ setSelectionVersion(v => v + 1);
1971
2605
  triggerChange();
1972
2606
  };
1973
2607
  const removeMedia = () => {
@@ -1976,17 +2610,26 @@ function RichTextEditor({
1976
2610
  triggerChange();
1977
2611
  };
1978
2612
  const isActivePercent = percent => {
2613
+ if (selectedMedia.dataset.widthPx) return false;
1979
2614
  if (!selectedMedia.dataset.widthPercent && !(selectedMedia.style.width || "").endsWith("%")) {
1980
2615
  return false;
1981
2616
  }
1982
2617
  return Math.abs(currentPercent - percent) <= 3;
1983
2618
  };
2619
+ const isFormControl = target => target instanceof Element && !!target.closest("input, select, textarea");
1984
2620
  return /*#__PURE__*/React.createElement("div", {
1985
2621
  className: "rte-media-toolbar",
2622
+ onMouseDown: e => {
2623
+ e.stopPropagation();
2624
+ if (!isFormControl(e.target)) {
2625
+ e.preventDefault();
2626
+ }
2627
+ },
2628
+ onClick: e => e.stopPropagation(),
1986
2629
  style: {
1987
2630
  position: "absolute",
1988
- top: Math.max(0, top - 44),
1989
- left: Math.max(8, left + width / 2 - 156),
2631
+ top: Math.max(4, toolbarTop),
2632
+ left: Math.max(8, Math.min(relLeft + mediaWidth / 2 - toolbarWidth / 2, anchorRect.width - toolbarWidth - 8)),
1990
2633
  zIndex: 1000
1991
2634
  }
1992
2635
  }, /*#__PURE__*/React.createElement("button", {
@@ -2011,6 +2654,44 @@ function RichTextEditor({
2011
2654
  title: `${percent}% width`
2012
2655
  }, percent, "%")), /*#__PURE__*/React.createElement("span", {
2013
2656
  className: "rte-media-toolbar-divider"
2657
+ }), /*#__PURE__*/React.createElement("div", {
2658
+ className: "rte-media-width-custom"
2659
+ }, /*#__PURE__*/React.createElement("input", {
2660
+ type: "number",
2661
+ className: "rte-media-width-input",
2662
+ value: mediaWidthInput,
2663
+ min: mediaWidthUnit === "%" ? 10 : 20,
2664
+ max: mediaWidthUnit === "%" ? 100 : 2000,
2665
+ title: "Custom width",
2666
+ onChange: e => setMediaWidthInput(e.target.value),
2667
+ onBlur: e => {
2668
+ var _e$relatedTarget, _e$relatedTarget$clos;
2669
+ 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;
2670
+ applyCustomMediaWidth();
2671
+ },
2672
+ onMouseDown: e => e.stopPropagation(),
2673
+ onKeyDown: e => {
2674
+ if (e.key === "Enter") {
2675
+ e.preventDefault();
2676
+ applyCustomMediaWidth();
2677
+ }
2678
+ }
2679
+ }), /*#__PURE__*/React.createElement("select", {
2680
+ className: "rte-media-width-unit",
2681
+ value: mediaWidthUnit,
2682
+ onChange: e => handleUnitChange(e.target.value),
2683
+ onMouseDown: e => e.stopPropagation()
2684
+ }, /*#__PURE__*/React.createElement("option", {
2685
+ value: "%"
2686
+ }, "%"), /*#__PURE__*/React.createElement("option", {
2687
+ value: "px"
2688
+ }, "px")), /*#__PURE__*/React.createElement("button", {
2689
+ type: "button",
2690
+ className: "rte-media-width-apply",
2691
+ title: "Apply size",
2692
+ onClick: applyCustomMediaWidth
2693
+ }, "\u2713")), /*#__PURE__*/React.createElement("span", {
2694
+ className: "rte-media-toolbar-divider"
2014
2695
  }), /*#__PURE__*/React.createElement("button", {
2015
2696
  type: "button",
2016
2697
  onClick: removeMedia,
@@ -2145,7 +2826,7 @@ function RichTextEditor({
2145
2826
  key: s,
2146
2827
  value: s
2147
2828
  }, s, "px"))), /*#__PURE__*/React.createElement("label", {
2148
- title: "Font Color",
2829
+ title: "Text Color",
2149
2830
  className: "rte-color-picker-label"
2150
2831
  }, /*#__PURE__*/React.createElement(FaFont, {
2151
2832
  size: 14,
@@ -2158,12 +2839,68 @@ function RichTextEditor({
2158
2839
  onMouseDown: e => {
2159
2840
  e.preventDefault();
2160
2841
  e.stopPropagation();
2842
+ saveSelection();
2161
2843
  },
2162
2844
  onChange: e => {
2163
2845
  e.stopPropagation();
2164
- handleColorChange(e.target.value);
2846
+ applyTextColor(e.target.value);
2165
2847
  },
2166
2848
  className: "rte-color-input"
2849
+ })), /*#__PURE__*/React.createElement("label", {
2850
+ title: areaHighlightMode ? "Highlight color for area selection" : "Text background color",
2851
+ className: "rte-color-picker-label rte-bg-color-picker-label"
2852
+ }, /*#__PURE__*/React.createElement("span", {
2853
+ className: `rte-bg-color-swatch${hasActiveTextBackground ? "" : " rte-bg-color-swatch-none"}`,
2854
+ style: hasActiveTextBackground ? {
2855
+ backgroundColor: bgColor,
2856
+ color: fontColor
2857
+ } : {
2858
+ color: fontColor
2859
+ }
2860
+ }, "A"), /*#__PURE__*/React.createElement("input", {
2861
+ type: "color",
2862
+ value: bgColor,
2863
+ onMouseDown: e => {
2864
+ e.preventDefault();
2865
+ e.stopPropagation();
2866
+ if (!areaHighlightMode) saveSelection();
2867
+ },
2868
+ onChange: e => {
2869
+ e.stopPropagation();
2870
+ const color = e.target.value;
2871
+ setBgColor(color);
2872
+ if (!areaHighlightMode) {
2873
+ applyBackgroundColor(color);
2874
+ }
2875
+ },
2876
+ className: "rte-color-input"
2877
+ })), /*#__PURE__*/React.createElement("button", {
2878
+ type: "button",
2879
+ title: "Remove background color",
2880
+ className: `rte-toolbar-button rte-bg-color-clear${hasActiveTextBackground ? " active" : ""}`,
2881
+ onMouseDown: e => {
2882
+ e.preventDefault();
2883
+ e.stopPropagation();
2884
+ saveSelection();
2885
+ removeBackgroundColor();
2886
+ }
2887
+ }, /*#__PURE__*/React.createElement("span", {
2888
+ className: "rte-bg-color-clear-icon",
2889
+ "aria-hidden": "true"
2890
+ }, "A")), /*#__PURE__*/React.createElement("button", {
2891
+ type: "button",
2892
+ title: "Area highlight \u2014 drag to select a box (like screenshot)",
2893
+ className: `rte-toolbar-button rte-area-highlight-toggle${areaHighlightMode ? " active" : ""}`,
2894
+ onMouseDown: e => e.preventDefault(),
2895
+ onClick: () => {
2896
+ setAreaHighlightMode(prev => !prev);
2897
+ setMarqueePreview(null);
2898
+ areaDragRef.current = null;
2899
+ focus();
2900
+ }
2901
+ }, /*#__PURE__*/React.createElement("span", {
2902
+ className: "rte-area-highlight-icon",
2903
+ "aria-hidden": "true"
2167
2904
  })), /*#__PURE__*/React.createElement("div", {
2168
2905
  style: {
2169
2906
  width: '1px',
@@ -2460,11 +3197,26 @@ function RichTextEditor({
2460
3197
  }
2461
3198
  return null;
2462
3199
  })()), /*#__PURE__*/React.createElement("div", {
2463
- className: "rte-content-wrapper",
3200
+ ref: contentWrapperRef,
3201
+ className: `rte-content-wrapper${areaHighlightMode ? " rte-area-highlight-mode" : ""}`,
2464
3202
  style: {
2465
3203
  position: 'relative'
3204
+ },
3205
+ onMouseDown: handleAreaSelectMouseDown
3206
+ }, areaHighlightMode && /*#__PURE__*/React.createElement("div", {
3207
+ className: "rte-area-highlight-hint",
3208
+ "aria-hidden": "true"
3209
+ }, "Drag to select an area, then release to apply highlight"), marqueePreview && /*#__PURE__*/React.createElement("div", {
3210
+ className: "rte-marquee-preview",
3211
+ style: {
3212
+ left: marqueePreview.left,
3213
+ top: marqueePreview.top,
3214
+ width: marqueePreview.width,
3215
+ height: marqueePreview.height,
3216
+ backgroundColor: `${bgColor}55`,
3217
+ borderColor: bgColor
2466
3218
  }
2467
- }, /*#__PURE__*/React.createElement("div", {
3219
+ }), /*#__PURE__*/React.createElement("div", {
2468
3220
  ref: editorRef,
2469
3221
  contentEditable: editable && disabled !== true,
2470
3222
  suppressContentEditableWarning: true,
@@ -2477,6 +3229,11 @@ function RichTextEditor({
2477
3229
  onDragStart: e => e.preventDefault(),
2478
3230
  onDragOver: e => e.preventDefault(),
2479
3231
  onKeyDown: handleKeyDown,
3232
+ onKeyUp: handleKeyUp,
3233
+ onMouseUp: () => {
3234
+ saveSelection();
3235
+ setSelectionVersion(v => v + 1);
3236
+ },
2480
3237
  onClick: handleEditorClick,
2481
3238
  onFocus: handleEditorFocus,
2482
3239
  onBlur: handleEditorBlur,
@@ -2492,7 +3249,7 @@ function RichTextEditor({
2492
3249
  left: paddingLeft || '12px'
2493
3250
  },
2494
3251
  "aria-hidden": "true"
2495
- }, placeholder)), renderMediaToolbar(), /*#__PURE__*/React.createElement("div", {
3252
+ }, placeholder), renderMediaToolbar()), /*#__PURE__*/React.createElement("div", {
2496
3253
  className: "rte-footer"
2497
3254
  }, /*#__PURE__*/React.createElement("div", {
2498
3255
  className: "rte-footer-content"