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