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

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
@@ -231,6 +231,27 @@ const FaFont = ({
231
231
  }
232
232
  });
233
233
  };
234
+ const FaLink = ({
235
+ className,
236
+ size,
237
+ color,
238
+ style
239
+ }) => {
240
+ return /*#__PURE__*/React.createElement("span", {
241
+ className: className,
242
+ style: {
243
+ display: 'inline-flex',
244
+ alignItems: 'center',
245
+ justifyContent: 'center',
246
+ fontSize: size || '1em',
247
+ color: color || 'inherit',
248
+ ...style
249
+ },
250
+ dangerouslySetInnerHTML: {
251
+ __html: `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M326.612 185.391c59.747 59.809 58.927 155.698.36 214.59-.11.12-.24.25-.36.37l-67.2 67.2c-59.27 59.27-155.699 59.262-214.96 0-59.27-59.26-59.27-155.7 0-214.96l37.106-37.106c9.84-9.84 26.786-3.3 27.294 10.606.648 17.722 3.826 35.527 9.69 52.721 1.986 5.822.567 12.262-3.783 16.612l-13.087 13.087c-28.026 28.026-28.905 73.66-1.155 101.96 28.024 28.579 74.086 28.749 102.325.51l67.2-67.19c28.191-28.191 28.073-73.757 0-101.83-3.701-3.694-7.429-6.564-10.341-8.569a16.037 16.037 0 0 1-6.947-12.606c-.396-10.567 3.348-21.456 11.698-29.806l21.054-21.055c5.521-5.521 14.182-6.199 20.584-1.731a152.482 152.482 0 0 1 20.522 17.197zM467.547 44.449c-59.261-59.262-155.69-59.27-214.96 0l-67.2 67.2c-.12.12-.25.25-.36.37-58.566 58.892-59.387 154.781.36 214.59a152.454 152.454 0 0 0 20.521 17.196c6.402 4.468 15.064 3.789 20.584-1.731l21.054-21.055c8.35-8.35 12.094-19.239 11.698-29.806a16.037 16.037 0 0 0-6.947-12.606c-2.912-2.005-6.64-4.875-10.341-8.569-28.073-28.073-28.191-73.639 0-101.83l67.2-67.19c28.239-28.239 74.3-28.069 102.325.51 27.75 28.3 26.872 73.934-1.155 101.96l-13.087 13.087c-4.35 4.35-5.769 10.79-3.783 16.612 5.864 17.194 9.042 34.999 9.69 52.721.509 13.906 17.454 20.446 27.294 10.606l37.106-37.106c59.271-59.259 59.271-155.699.001-214.959z"></path></svg>`
252
+ }
253
+ });
254
+ };
234
255
  const FaTable = ({
235
256
  className,
236
257
  size,
@@ -388,7 +409,7 @@ function styleInject(css, ref) {
388
409
  }
389
410
  }
390
411
 
391
- 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-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 ul{list-style-type:disc;margin-left:1.5rem}.rte-content ol{list-style-type:decimal;margin-left:1.5rem}.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);height:0;margin:16px 0;max-width:100%;overflow:hidden;padding-bottom:56.25%;position:relative}.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;outline:none;padding:8px 12px;transition:all .15s ease;width:93%}.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%;width:fit-content}.image-container.image-align-center{margin-left:auto;margin-right:auto}.image-container.image-align-left{margin-left:0;margin-right:auto}.image-container.image-align-right{margin-left:auto;margin-right:0}.image-media-frame{display:block;line-height:0;max-width:100%;position:relative;width:fit-content}.image-media-frame img{border-radius:12px;display:block;height:auto;margin:0;max-width:100%;width:auto}.image-media-frame[style*=width] img{width:100%}.image-media-frame[data-explicit-height=true] img,.image-media-frame[style*=height] img{height:100%;object-fit:contain}.image-container.image-small .image-media-frame img{width:50%!important}.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}.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-handles{inset:0;pointer-events:none;position:absolute;z-index:55}.media-resize-handle{background:#dbeafe;border:2px solid #fff;border-radius:4px;box-shadow:0 1px 3px rgba(15,23,42,.12);pointer-events:auto;position:absolute;z-index:60}.media-resize-handle:hover{background:#bfdbfe}.media-resize-handle-left,.media-resize-handle-right{cursor:ew-resize;height:28px;top:50%;transform:translateY(-50%);width:10px}.media-resize-handle-right{right:-5px}.media-resize-handle-left{left:-5px}.media-resize-handle-bottom,.media-resize-handle-top{cursor:ns-resize;height:10px;left:50%;transform:translateX(-50%);width:28px}.media-resize-handle-top{top:-5px}.media-resize-handle-bottom{bottom:-5px}.video-container .media-resize-handle-right{right:4px}.video-container .media-resize-handle-left{left:4px}.video-container .media-resize-handle-top{top:4px}.video-container .media-resize-handle-bottom{bottom:4px}.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-image-toolbar{background:#fff!important;border:1px solid #e5e7eb!important;border-radius:8px!important;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06)!important;display:flex;gap:4px!important;padding:4px!important;pointer-events:auto!important}.rte-image-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:32px;padding:0 4px;transition:all .15s ease}.rte-image-toolbar button:hover{background-color:#f3f4f6;color:#111827}.rte-image-toolbar button.danger{color:#ef4444!important}.rte-image-toolbar button.danger:hover{background-color:#fef2f2!important}.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-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}";
392
413
  styleInject(css_248z);
393
414
 
394
415
  // Helper functions for HTML escaping
@@ -405,11 +426,12 @@ function RichTextEditor({
405
426
  showEditButton,
406
427
  onBlur,
407
428
  disabled = false,
408
- editable: initialEditable = false,
429
+ editable: initialEditable = true,
409
430
  value,
410
431
  isLoading,
411
432
  isList = false,
412
433
  label,
434
+ placeholder = "Type here...",
413
435
  showBorder = true,
414
436
  paddingLeft,
415
437
  minHeight,
@@ -446,6 +468,7 @@ function RichTextEditor({
446
468
  // NEW: Track current line height
447
469
  const [currentLineHeight, setCurrentLineHeight] = useState("");
448
470
  const [activeAlign, setActiveAlign] = useState(null);
471
+ const [currentBlockFormat, setCurrentBlockFormat] = useState("div");
449
472
  const [imageModalOpen, setImageModalOpen] = useState(false);
450
473
  const [selectedImageUrl, setSelectedImageUrl] = useState("");
451
474
  const [zoomLevel, setZoomLevel] = useState(1);
@@ -457,11 +480,12 @@ function RichTextEditor({
457
480
  const [tableRows, setTableRows] = useState(3);
458
481
  const [tableCols, setTableCols] = useState(3);
459
482
  const [selectionVersion, setSelectionVersion] = useState(0);
460
- const [selectedImage, setSelectedImage] = useState(null);
483
+ const [selectedMedia, setSelectedMedia] = useState(null);
461
484
  const [metrics, setMetrics] = useState({
462
485
  words: 0,
463
486
  chars: 0
464
487
  });
488
+ const [isEmpty, setIsEmpty] = useState(!value);
465
489
  const updateMetrics = useCallback(() => {
466
490
  if (!editorRef.current) return;
467
491
  // Calculate metrics immediately but outside of render path
@@ -473,6 +497,11 @@ function RichTextEditor({
473
497
  words,
474
498
  chars
475
499
  });
500
+
501
+ // Track emptiness for the placeholder. Account for media-only content.
502
+ const stripped = text.replace(/[\u200B\u00A0\s]/g, "");
503
+ const hasMedia = !!editorRef.current.querySelector("img, table, iframe");
504
+ setIsEmpty(stripped.length === 0 && !hasMedia);
476
505
  }, []);
477
506
  const openImageModal = url => {
478
507
  if (editorRef.current) {
@@ -537,11 +566,10 @@ function RichTextEditor({
537
566
  requestAnimationFrame(() => syncProcessedMediaRef.current(editorRef.current));
538
567
  }
539
568
  }, [value]);
540
-
541
- // Runs whenever editable changes (toggles delete icon visibility)
542
569
  useEffect(() => {
543
570
  if (!editable) {
544
571
  setEditorFocused(false);
572
+ clearMediaSelection();
545
573
  }
546
574
  syncProcessedMediaRef.current(editorRef.current);
547
575
  }, [editable]);
@@ -705,17 +733,15 @@ function RichTextEditor({
705
733
  return rgbToHex(computedColor);
706
734
  };
707
735
  const stripEditorChrome = root => {
708
- root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handles, .media-resize-handle").forEach(element => element.remove());
736
+ root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handle").forEach(element => element.remove());
737
+ root.querySelectorAll(".rte-media-selected").forEach(element => {
738
+ element.classList.remove("rte-media-selected");
739
+ });
709
740
  return root;
710
741
  };
711
- const getMediaSizeLimits = () => {
712
- const maxWidth = editorRef.current ? editorRef.current.getBoundingClientRect().width - 24 : 800;
713
- return {
714
- minWidth: 120,
715
- minHeight: 80,
716
- maxWidth,
717
- maxHeight: 720
718
- };
742
+ const getEditorInnerWidth = () => {
743
+ if (!editorRef.current) return 800;
744
+ return Math.max(editorRef.current.clientWidth - 24, 200);
719
745
  };
720
746
  const ensureImageMediaFrame = imageContainer => {
721
747
  if (!imageContainer) return null;
@@ -723,159 +749,163 @@ function RichTextEditor({
723
749
  if (frame) return frame;
724
750
  frame = document.createElement("div");
725
751
  frame.className = "image-media-frame";
726
- ["width", "height", "marginLeft", "marginTop", "maxWidth"].forEach(prop => {
727
- if (imageContainer.style[prop]) {
728
- frame.style[prop] = imageContainer.style[prop];
729
- imageContainer.style[prop] = "";
730
- }
731
- });
732
- if (imageContainer.dataset.explicitHeight) {
733
- frame.dataset.explicitHeight = imageContainer.dataset.explicitHeight;
734
- delete imageContainer.dataset.explicitHeight;
735
- }
736
752
  const children = Array.from(imageContainer.children);
737
753
  imageContainer.appendChild(frame);
738
754
  children.forEach(child => frame.appendChild(child));
739
755
  return frame;
740
756
  };
741
757
  const getImageMediaTarget = imageContainer => ensureImageMediaFrame(imageContainer) || imageContainer;
742
- const applyImageMediaSize = (frame, width, height, edge) => {
758
+ const getMediaWidthPercent = container => {
759
+ if (!container) return 100;
760
+ if (container.dataset.widthPercent) {
761
+ return Number(container.dataset.widthPercent);
762
+ }
763
+ const width = container.style.width || "";
764
+ if (width.endsWith("%")) {
765
+ return parseInt(width, 10) || 100;
766
+ }
767
+ const editorWidth = getEditorInnerWidth();
768
+ const rect = container.getBoundingClientRect();
769
+ if (editorWidth > 0 && rect.width > 0) {
770
+ return Math.round(rect.width / editorWidth * 100);
771
+ }
772
+ return 100;
773
+ };
774
+ const applyMediaWidthPercent = (container, percent) => {
775
+ if (!container) return;
776
+ const clamped = Math.max(25, Math.min(100, Math.round(percent)));
777
+ container.dataset.widthPercent = String(clamped);
778
+ container.style.width = `${clamped}%`;
779
+ container.style.maxWidth = "100%";
780
+ container.style.marginLeft = "";
781
+ container.style.marginTop = "";
782
+ if (container.classList.contains("video-container")) {
783
+ container.style.height = "0";
784
+ container.style.paddingBottom = "56.25%";
785
+ return;
786
+ }
787
+ const frame = getImageMediaTarget(container);
788
+ if (!frame) return;
789
+ frame.style.width = "100%";
790
+ frame.style.height = "";
743
791
  const img = frame.querySelector("img");
744
- const isVertical = edge === "top" || edge === "bottom";
745
- frame.style.width = `${Math.round(width)}px`;
746
- frame.style.maxWidth = "100%";
747
- if (isVertical) {
748
- frame.style.height = `${Math.round(height)}px`;
749
- frame.dataset.explicitHeight = "true";
750
- if (img) {
751
- img.style.width = "100%";
752
- img.style.height = "100%";
753
- img.style.objectFit = "contain";
754
- }
792
+ if (img) {
793
+ img.style.height = "auto";
794
+ img.style.objectFit = "";
795
+ }
796
+ };
797
+ const normalizeMediaWidth = container => {
798
+ if (!container) return;
799
+ if (container.classList.contains("image-small")) {
800
+ container.classList.remove("image-small");
801
+ applyMediaWidthPercent(container, 50);
802
+ return;
803
+ }
804
+ if (container.dataset.widthPercent) {
805
+ applyMediaWidthPercent(container, Number(container.dataset.widthPercent));
755
806
  return;
756
807
  }
757
- if (!frame.dataset.explicitHeight) {
758
- frame.style.height = "";
808
+ const width = container.style.width || "";
809
+ if (width.endsWith("%")) {
810
+ applyMediaWidthPercent(container, parseInt(width, 10) || 100);
811
+ return;
759
812
  }
760
- if (img) {
761
- img.style.width = "100%";
762
- if (frame.dataset.explicitHeight) {
763
- img.style.height = "100%";
764
- img.style.objectFit = "contain";
765
- } else {
766
- img.style.height = "auto";
767
- img.style.objectFit = "";
813
+ 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));
768
818
  }
819
+ return;
820
+ }
821
+ if (container.classList.contains("video-container")) {
822
+ applyMediaWidthPercent(container, 100);
769
823
  }
770
824
  };
771
- const applyVideoMediaSize = (container, width, height) => {
772
- container.style.paddingBottom = "0";
773
- container.style.width = `${Math.round(width)}px`;
774
- container.style.maxWidth = "100%";
775
- container.style.height = `${Math.round(height)}px`;
825
+ const clearMediaSelection = () => {
826
+ var _editorRef$current;
827
+ (_editorRef$current = editorRef.current) === null || _editorRef$current === void 0 || _editorRef$current.querySelectorAll(".rte-media-selected").forEach(element => {
828
+ element.classList.remove("rte-media-selected");
829
+ });
830
+ setSelectedMedia(null);
831
+ };
832
+ const selectMediaContainer = container => {
833
+ var _editorRef$current2;
834
+ if (!container || !((_editorRef$current2 = editorRef.current) !== null && _editorRef$current2 !== void 0 && _editorRef$current2.contains(container))) return;
835
+ editorRef.current.querySelectorAll(".rte-media-selected").forEach(element => {
836
+ element.classList.remove("rte-media-selected");
837
+ });
838
+ container.classList.add("rte-media-selected");
839
+ setSelectedMedia(container);
776
840
  };
777
841
  const attachMediaResizeHandle = container => {
778
- if (!container || container.querySelector(".media-resize-handles")) return;
779
- const isVideo = container.classList.contains("video-container");
780
- const resizeTarget = isVideo ? container : getImageMediaTarget(container);
842
+ var _resizeTarget$querySe;
843
+ if (!container) return;
844
+ const resizeTarget = container.classList.contains("video-container") ? container : getImageMediaTarget(container);
781
845
  if (!resizeTarget) return;
782
- const handlesWrapper = document.createElement("div");
783
- handlesWrapper.className = "media-resize-handles";
784
- handlesWrapper.setAttribute("contenteditable", "false");
785
- const edges = [{
786
- edge: "left",
787
- title: "Drag to resize width"
788
- }, {
789
- edge: "right",
790
- title: "Drag to resize width"
791
- }, {
792
- edge: "top",
793
- title: "Drag to resize height"
794
- }, {
795
- edge: "bottom",
796
- title: "Drag to resize height"
797
- }];
798
- edges.forEach(({
799
- edge,
800
- title
801
- }) => {
802
- const handle = document.createElement("div");
803
- handle.className = `media-resize-handle media-resize-handle-${edge}`;
804
- handle.title = title;
805
- handle.setAttribute("contenteditable", "false");
806
- handle.dataset.edge = edge;
807
- handle.addEventListener("mousedown", event => {
808
- if (!editable) return;
809
- event.preventDefault();
810
- event.stopPropagation();
811
- const limits = getMediaSizeLimits();
812
- const rect = resizeTarget.getBoundingClientRect();
813
- const startX = event.clientX;
814
- const startY = event.clientY;
815
- const startWidth = rect.width;
816
- const startHeight = rect.height;
817
- const startMarginLeft = Number.parseFloat(resizeTarget.style.marginLeft) || 0;
818
- const startMarginTop = Number.parseFloat(resizeTarget.style.marginTop) || 0;
819
- if (isVideo) {
820
- resizeTarget.style.paddingBottom = "0";
821
- }
822
- const onMouseMove = moveEvent => {
823
- const deltaX = moveEvent.clientX - startX;
824
- const deltaY = moveEvent.clientY - startY;
825
- let nextWidth = startWidth;
826
- let nextHeight = startHeight;
827
- if (edge === "right") {
828
- nextWidth = startWidth + deltaX;
829
- } else if (edge === "left") {
830
- nextWidth = startWidth - deltaX;
831
- } else if (edge === "bottom") {
832
- nextHeight = startHeight + deltaY;
833
- } else if (edge === "top") {
834
- nextHeight = startHeight - deltaY;
835
- }
836
- nextWidth = Math.max(limits.minWidth, Math.min(nextWidth, limits.maxWidth));
837
- nextHeight = Math.max(limits.minHeight, Math.min(nextHeight, limits.maxHeight));
838
- if (edge === "left") {
839
- resizeTarget.style.marginLeft = `${Math.round(startMarginLeft + (startWidth - nextWidth))}px`;
840
- }
841
- if (edge === "top") {
842
- resizeTarget.style.marginTop = `${Math.round(startMarginTop + (startHeight - nextHeight))}px`;
843
- }
844
- if (isVideo) {
845
- applyVideoMediaSize(resizeTarget, nextWidth, nextHeight);
846
- } else {
847
- applyImageMediaSize(resizeTarget, nextWidth, nextHeight, edge);
848
- }
849
- };
850
- const onMouseUp = () => {
851
- document.removeEventListener("mousemove", onMouseMove);
852
- document.removeEventListener("mouseup", onMouseUp);
853
- triggerChange();
854
- };
855
- document.addEventListener("mousemove", onMouseMove);
856
- document.addEventListener("mouseup", onMouseUp);
857
- });
858
- handlesWrapper.appendChild(handle);
846
+ (_resizeTarget$querySe = resizeTarget.querySelector(".media-resize-handle")) === null || _resizeTarget$querySe === void 0 || _resizeTarget$querySe.remove();
847
+ const handle = document.createElement("div");
848
+ handle.className = "media-resize-handle";
849
+ handle.title = "Drag to resize";
850
+ handle.setAttribute("contenteditable", "false");
851
+ handle.addEventListener("mousedown", event => {
852
+ if (!editable) return;
853
+ event.preventDefault();
854
+ event.stopPropagation();
855
+ selectMediaContainer(container);
856
+ const editorWidth = getEditorInnerWidth();
857
+ const startX = event.clientX;
858
+ const startWidth = container.getBoundingClientRect().width;
859
+ const onMouseMove = moveEvent => {
860
+ const nextWidth = Math.max(60, startWidth + (moveEvent.clientX - startX));
861
+ const percent = Math.round(nextWidth / editorWidth * 100);
862
+ applyMediaWidthPercent(container, percent);
863
+ };
864
+ const onMouseUp = () => {
865
+ document.removeEventListener("mousemove", onMouseMove);
866
+ document.removeEventListener("mouseup", onMouseUp);
867
+ triggerChange();
868
+ };
869
+ document.addEventListener("mousemove", onMouseMove);
870
+ document.addEventListener("mouseup", onMouseUp);
859
871
  });
860
- resizeTarget.appendChild(handlesWrapper);
872
+ resizeTarget.appendChild(handle);
861
873
  };
862
874
  const handleEditorFocus = () => {
863
875
  setEditorFocused(true);
864
876
  };
865
877
  const handleEditorBlur = () => {
866
878
  requestAnimationFrame(() => {
867
- var _editorRef$current;
868
- if (!((_editorRef$current = editorRef.current) !== null && _editorRef$current !== void 0 && _editorRef$current.contains(document.activeElement))) {
879
+ var _editorRef$current3;
880
+ if (!((_editorRef$current3 = editorRef.current) !== null && _editorRef$current3 !== void 0 && _editorRef$current3.contains(document.activeElement))) {
869
881
  setEditorFocused(false);
870
882
  }
871
883
  });
872
884
  };
873
885
  const updateMediaControlVisibility = container => {
874
- const handles = container.querySelector(".media-resize-handles");
875
- if (handles instanceof HTMLElement) {
876
- handles.style.display = editable ? "block" : "none";
877
- handles.style.pointerEvents = editable ? "auto" : "none";
886
+ const handle = container.querySelector(".media-resize-handle");
887
+ if (handle instanceof HTMLElement) {
888
+ handle.style.display = editable ? "block" : "none";
889
+ handle.style.pointerEvents = editable ? "auto" : "none";
890
+ }
891
+ };
892
+ const BLOCK_TAGS = ["P", "DIV", "H1", "H2", "H3", "BLOCKQUOTE", "LI"];
893
+ const getActiveBlock = node => {
894
+ if (!editorRef.current || !node) return null;
895
+ let current = node.nodeType === 3 ? node.parentNode : node;
896
+ while (current && current !== editorRef.current) {
897
+ if (current.nodeType === 1 && BLOCK_TAGS.includes(current.tagName)) {
898
+ return current;
899
+ }
900
+ current = current.parentNode;
878
901
  }
902
+ return editorRef.current;
903
+ };
904
+ const getBlockFormat = node => {
905
+ const block = getActiveBlock(node);
906
+ if (!block || block === editorRef.current) return "div";
907
+ const tag = block.tagName.toLowerCase();
908
+ return tag === "p" || tag === "li" ? "div" : tag;
879
909
  };
880
910
  const createMediaDeleteButton = (title, className, onRemove) => {
881
911
  const deleteBtn = document.createElement("button");
@@ -895,10 +925,10 @@ function RichTextEditor({
895
925
  // Listen for selection changes globally to update styles and list type in one pass
896
926
  useEffect(() => {
897
927
  const handleGlobalSelectionSync = () => {
898
- var _editorRef$current2;
928
+ var _editorRef$current4;
899
929
  // Only sync if the editor has focus
900
930
  const sel = window.getSelection();
901
- if (!sel || !sel.rangeCount || !((_editorRef$current2 = editorRef.current) !== null && _editorRef$current2 !== void 0 && _editorRef$current2.contains(sel.anchorNode))) {
931
+ if (!sel || !sel.rangeCount || !((_editorRef$current4 = editorRef.current) !== null && _editorRef$current4 !== void 0 && _editorRef$current4.contains(sel.anchorNode))) {
902
932
  return;
903
933
  }
904
934
 
@@ -946,6 +976,7 @@ function RichTextEditor({
946
976
  } else {
947
977
  setCurrentFontSize("16");
948
978
  }
979
+ setCurrentBlockFormat(getBlockFormat(sel.anchorNode));
949
980
  };
950
981
  document.addEventListener("selectionchange", handleGlobalSelectionSync);
951
982
  return () => {
@@ -1183,11 +1214,25 @@ function RichTextEditor({
1183
1214
  if (!videoContainer.querySelector(".video-delete-button")) {
1184
1215
  const deleteBtn = createMediaDeleteButton("Remove video", "video-delete-button image-delete-button", () => {
1185
1216
  videoContainer.remove();
1217
+ clearMediaSelection();
1186
1218
  triggerChange && triggerChange();
1187
1219
  });
1188
1220
  videoContainer.appendChild(deleteBtn);
1189
1221
  }
1222
+ if (!videoContainer.dataset.mediaEnhanced) {
1223
+ videoContainer.dataset.mediaEnhanced = "true";
1224
+ if (!videoContainer.classList.contains("image-align-left") && !videoContainer.classList.contains("image-align-center") && !videoContainer.classList.contains("image-align-right")) {
1225
+ videoContainer.classList.add("image-align-left");
1226
+ }
1227
+ videoContainer.addEventListener("click", event => {
1228
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1229
+ event.preventDefault();
1230
+ event.stopPropagation();
1231
+ selectMediaContainer(videoContainer);
1232
+ });
1233
+ }
1190
1234
  attachMediaResizeHandle(videoContainer);
1235
+ normalizeMediaWidth(videoContainer);
1191
1236
  updateMediaControlVisibility(videoContainer);
1192
1237
  });
1193
1238
  };
@@ -1207,6 +1252,7 @@ function RichTextEditor({
1207
1252
  }));
1208
1253
  }
1209
1254
  attachMediaResizeHandle(existingWrapper);
1255
+ normalizeMediaWidth(existingWrapper);
1210
1256
  updateMediaControlVisibility(existingWrapper);
1211
1257
  return;
1212
1258
  }
@@ -1216,24 +1262,21 @@ function RichTextEditor({
1216
1262
  wrapper.style.cursor = editable ? "pointer" : "default";
1217
1263
  const frame = document.createElement("div");
1218
1264
  frame.className = "image-media-frame";
1219
- if (img.getAttribute("width") && !frame.style.width) {
1220
- frame.style.width = `${img.getAttribute("width")}px`;
1221
- } else if (img.style.width && img.style.width.endsWith("px")) {
1222
- frame.style.width = img.style.width;
1223
- }
1224
1265
  img.classList.add("rte-image");
1225
1266
  img.setAttribute("data-align", align);
1226
- if (frame.style.width) {
1227
- img.style.width = "100%";
1228
- img.style.height = frame.dataset.explicitHeight ? "100%" : "auto";
1229
- } else {
1230
- img.style.width = "";
1231
- img.style.height = "auto";
1232
- }
1233
- img.addEventListener("click", event => {
1267
+ img.style.height = "auto";
1268
+ img.addEventListener("dblclick", event => {
1234
1269
  if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1270
+ event.preventDefault();
1271
+ event.stopPropagation();
1235
1272
  openImageModal(img.src);
1236
1273
  });
1274
+ img.addEventListener("click", event => {
1275
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1276
+ event.preventDefault();
1277
+ event.stopPropagation();
1278
+ selectMediaContainer(wrapper);
1279
+ });
1237
1280
  const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
1238
1281
  wrapper.remove();
1239
1282
  triggerChange && triggerChange();
@@ -1248,6 +1291,7 @@ function RichTextEditor({
1248
1291
  frame.appendChild(deleteBtn);
1249
1292
  wrapper.appendChild(frame);
1250
1293
  attachMediaResizeHandle(wrapper);
1294
+ normalizeMediaWidth(wrapper);
1251
1295
  if (nextSibling) {
1252
1296
  parentNode.insertBefore(wrapper, nextSibling);
1253
1297
  } else {
@@ -1274,9 +1318,19 @@ function RichTextEditor({
1274
1318
  const img = document.createElement('img');
1275
1319
  img.src = dataUrl;
1276
1320
  img.alt = fileName || "image";
1277
- img.addEventListener("click", () => openImageModal(dataUrl));
1321
+ img.addEventListener("dblclick", event => {
1322
+ event.preventDefault();
1323
+ event.stopPropagation();
1324
+ openImageModal(dataUrl);
1325
+ });
1326
+ img.addEventListener("click", event => {
1327
+ event.preventDefault();
1328
+ event.stopPropagation();
1329
+ selectMediaContainer(container);
1330
+ });
1278
1331
  frame.appendChild(img);
1279
1332
  container.appendChild(frame);
1333
+ applyMediaWidthPercent(container, 25);
1280
1334
 
1281
1335
  // Insert at cursor position
1282
1336
  insertNodeAtCursor(container);
@@ -1449,6 +1503,8 @@ function RichTextEditor({
1449
1503
  selection.addRange(newRange);
1450
1504
  };
1451
1505
  const handleKeyDown = useCallback(e => {
1506
+ if (applyMarkdownShortcut(e)) return;
1507
+
1452
1508
  // Handle Enter key
1453
1509
  if (e.key === 'Enter') {
1454
1510
  e.preventDefault();
@@ -1520,7 +1576,7 @@ function RichTextEditor({
1520
1576
  return;
1521
1577
  }
1522
1578
  if (e.key === "Backspace") {
1523
- var _node$closest, _node, _editorRef$current3;
1579
+ var _node$closest, _node, _editorRef$current5;
1524
1580
  const selection = window.getSelection();
1525
1581
  if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
1526
1582
  const range = selection.getRangeAt(0);
@@ -1530,7 +1586,7 @@ function RichTextEditor({
1530
1586
  node = node.parentNode;
1531
1587
  }
1532
1588
  const listItem = (_node$closest = (_node = node).closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(_node, "li");
1533
- if (!listItem || !((_editorRef$current3 = editorRef.current) !== null && _editorRef$current3 !== void 0 && _editorRef$current3.contains(listItem))) return;
1589
+ if (!listItem || !((_editorRef$current5 = editorRef.current) !== null && _editorRef$current5 !== void 0 && _editorRef$current5.contains(listItem))) return;
1534
1590
  const list = listItem.parentNode;
1535
1591
  if (isListItemEffectivelyEmpty(listItem)) {
1536
1592
  e.preventDefault();
@@ -1607,6 +1663,65 @@ function RichTextEditor({
1607
1663
  const handleSelect = type => {
1608
1664
  exec(type === "unordered" ? "insertUnorderedList" : "insertOrderedList");
1609
1665
  };
1666
+ const applyBlockFormat = format => {
1667
+ document.execCommand("formatBlock", false, format);
1668
+ setCurrentBlockFormat(format);
1669
+ triggerChange();
1670
+ focus();
1671
+ };
1672
+ const clearFormatting = () => {
1673
+ document.execCommand("removeFormat", false, null);
1674
+ document.execCommand("unlink", false, null);
1675
+ document.execCommand("formatBlock", false, "div");
1676
+ setCurrentBlockFormat("div");
1677
+ setCurrentFontSize("16");
1678
+ setCurrentLineHeight("");
1679
+ setFontColor("#000000");
1680
+ triggerChange();
1681
+ focus();
1682
+ };
1683
+ const deleteTextBeforeCursorInBlock = (block, range, selection) => {
1684
+ const prefixRange = document.createRange();
1685
+ prefixRange.setStart(block, 0);
1686
+ prefixRange.setEnd(range.startContainer, range.startOffset);
1687
+ prefixRange.deleteContents();
1688
+ const nextRange = document.createRange();
1689
+ nextRange.setStart(block, 0);
1690
+ nextRange.collapse(true);
1691
+ selection.removeAllRanges();
1692
+ selection.addRange(nextRange);
1693
+ };
1694
+ const applyMarkdownShortcut = event => {
1695
+ if (event.key !== " " || event.ctrlKey || event.metaKey || event.altKey) {
1696
+ return false;
1697
+ }
1698
+ const selection = window.getSelection();
1699
+ if (!(selection !== null && selection !== void 0 && selection.rangeCount) || !selection.isCollapsed || !editorRef.current) {
1700
+ return false;
1701
+ }
1702
+ const range = selection.getRangeAt(0);
1703
+ const block = getActiveBlock(range.startContainer);
1704
+ if (!block || !editorRef.current.contains(block)) return false;
1705
+ const prefixRange = document.createRange();
1706
+ prefixRange.setStart(block, 0);
1707
+ prefixRange.setEnd(range.startContainer, range.startOffset);
1708
+ const textBeforeCursor = prefixRange.toString().replace(/\u00A0/g, " ").trim();
1709
+ const shortcuts = {
1710
+ "#": () => applyBlockFormat("h1"),
1711
+ "##": () => applyBlockFormat("h2"),
1712
+ "###": () => applyBlockFormat("h3"),
1713
+ ">": () => applyBlockFormat("blockquote"),
1714
+ "-": () => handleSelect("unordered"),
1715
+ "*": () => handleSelect("unordered"),
1716
+ "1.": () => handleSelect("ordered")
1717
+ };
1718
+ const action = shortcuts[textBeforeCursor];
1719
+ if (!action) return false;
1720
+ event.preventDefault();
1721
+ deleteTextBeforeCursorInBlock(block, range, selection);
1722
+ action();
1723
+ return true;
1724
+ };
1610
1725
  const onLineHeightChange = value => {
1611
1726
  if (!value) return;
1612
1727
  const sel = window.getSelection();
@@ -1788,6 +1903,7 @@ function RichTextEditor({
1788
1903
  }
1789
1904
  }, [disabled]);
1790
1905
  const handleEditorClick = useCallback(e => {
1906
+ var _editorRef$current6;
1791
1907
  setSelectionVersion(v => v + 1);
1792
1908
  const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
1793
1909
  if (deleteBtn && editable && editorFocused) {
@@ -1796,10 +1912,15 @@ function RichTextEditor({
1796
1912
  const wrapper = deleteBtn.closest('.image-container, .video-container');
1797
1913
  if (wrapper) {
1798
1914
  wrapper.remove();
1915
+ clearMediaSelection();
1799
1916
  triggerChange();
1800
1917
  }
1801
1918
  return;
1802
1919
  }
1920
+ 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) {
1922
+ return;
1923
+ }
1803
1924
 
1804
1925
  // Check if the click is on a link
1805
1926
  const clickedLink = e.target.closest('a');
@@ -1809,13 +1930,8 @@ function RichTextEditor({
1809
1930
  window.open(clickedLink.href, '_blank');
1810
1931
  return;
1811
1932
  }
1812
-
1813
- // NEW: Check if click is on an image for resizing
1814
- const clickedImg = e.target.closest('img');
1815
- if (clickedImg && !clickedImg.closest('.rte-modal')) {
1816
- setSelectedImage(clickedImg);
1817
- } else if (!e.target.closest('.rte-image-toolbar')) {
1818
- setSelectedImage(null);
1933
+ if (!e.target.closest('.rte-media-toolbar')) {
1934
+ clearMediaSelection();
1819
1935
  }
1820
1936
 
1821
1937
  // If disabled is true, prevent editing
@@ -1834,73 +1950,72 @@ function RichTextEditor({
1834
1950
  }, 0);
1835
1951
  }
1836
1952
  }, [editable, disabled, editorFocused, triggerChange]);
1837
- const renderImageToolbar = () => {
1838
- if (!selectedImage || !editorRef.current || !editable) return null;
1953
+ const renderMediaToolbar = () => {
1954
+ if (!selectedMedia || !editorRef.current || !editable) return null;
1839
1955
  const editorRect = editorRef.current.getBoundingClientRect();
1840
- const imgRect = selectedImage.getBoundingClientRect();
1841
- const top = imgRect.top - editorRect.top + editorRef.current.scrollTop;
1842
- const left = imgRect.left - editorRect.left + editorRef.current.scrollLeft;
1843
- const width = imgRect.width;
1956
+ 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;
1960
+ const currentPercent = getMediaWidthPercent(selectedMedia);
1961
+ const widthPresets = [25, 50, 75, 100];
1844
1962
  const handleAlignment = align => {
1845
- const wrapper = selectedImage.closest('.image-container');
1846
- if (wrapper) {
1847
- // Remove all alignment classes first
1848
- wrapper.classList.remove('image-align-left', 'image-align-center', 'image-align-right');
1849
- // Add the new alignment class
1850
- wrapper.classList.add(`image-align-${align}`);
1851
- selectedImage.setAttribute('data-align', align);
1852
- triggerChange();
1853
- }
1963
+ selectedMedia.classList.remove("image-align-left", "image-align-center", "image-align-right");
1964
+ selectedMedia.classList.add(`image-align-${align}`);
1965
+ const img = selectedMedia.querySelector("img");
1966
+ if (img) img.setAttribute("data-align", align);
1967
+ triggerChange();
1854
1968
  };
1855
- const removeImage = () => {
1856
- const wrapper = selectedImage.closest('.image-container');
1857
- if (wrapper) {
1858
- wrapper.remove();
1859
- setSelectedImage(null);
1860
- triggerChange();
1861
- }
1969
+ const setWidth = percent => {
1970
+ applyMediaWidthPercent(selectedMedia, percent);
1971
+ triggerChange();
1862
1972
  };
1863
- const toggleSize = () => {
1864
- const wrapper = selectedImage.closest('.image-container');
1865
- if (wrapper) {
1866
- const isSmall = wrapper.classList.contains('image-small');
1867
- if (isSmall) {
1868
- wrapper.classList.remove('image-small');
1869
- } else {
1870
- wrapper.classList.add('image-small');
1871
- }
1872
- triggerChange();
1973
+ const removeMedia = () => {
1974
+ selectedMedia.remove();
1975
+ clearMediaSelection();
1976
+ triggerChange();
1977
+ };
1978
+ const isActivePercent = percent => {
1979
+ if (!selectedMedia.dataset.widthPercent && !(selectedMedia.style.width || "").endsWith("%")) {
1980
+ return false;
1873
1981
  }
1982
+ return Math.abs(currentPercent - percent) <= 3;
1874
1983
  };
1875
1984
  return /*#__PURE__*/React.createElement("div", {
1876
- className: "rte-image-toolbar",
1985
+ className: "rte-media-toolbar",
1877
1986
  style: {
1878
- position: 'absolute',
1879
- top: Math.max(0, top - 45),
1880
- left: Math.max(0, left + width / 2 - 80),
1987
+ position: "absolute",
1988
+ top: Math.max(0, top - 44),
1989
+ left: Math.max(8, left + width / 2 - 156),
1881
1990
  zIndex: 1000
1882
1991
  }
1883
1992
  }, /*#__PURE__*/React.createElement("button", {
1884
1993
  type: "button",
1885
- onClick: () => handleAlignment('left'),
1994
+ onClick: () => handleAlignment("left"),
1886
1995
  title: "Align Left"
1887
1996
  }, "L"), /*#__PURE__*/React.createElement("button", {
1888
1997
  type: "button",
1889
- onClick: () => handleAlignment('center'),
1998
+ onClick: () => handleAlignment("center"),
1890
1999
  title: "Align Center"
1891
2000
  }, "C"), /*#__PURE__*/React.createElement("button", {
1892
2001
  type: "button",
1893
- onClick: () => handleAlignment('right'),
2002
+ onClick: () => handleAlignment("right"),
1894
2003
  title: "Align Right"
1895
- }, "R"), /*#__PURE__*/React.createElement("button", {
2004
+ }, "R"), /*#__PURE__*/React.createElement("span", {
2005
+ className: "rte-media-toolbar-divider"
2006
+ }), widthPresets.map(percent => /*#__PURE__*/React.createElement("button", {
2007
+ key: percent,
1896
2008
  type: "button",
1897
- onClick: toggleSize,
1898
- title: "Toggle 50% Width"
1899
- }, "50%"), /*#__PURE__*/React.createElement("button", {
2009
+ className: isActivePercent(percent) ? "active" : "",
2010
+ onClick: () => setWidth(percent),
2011
+ title: `${percent}% width`
2012
+ }, percent, "%")), /*#__PURE__*/React.createElement("span", {
2013
+ className: "rte-media-toolbar-divider"
2014
+ }), /*#__PURE__*/React.createElement("button", {
1900
2015
  type: "button",
1901
- onClick: removeImage,
2016
+ onClick: removeMedia,
1902
2017
  className: "danger",
1903
- title: "Remove Image"
2018
+ title: "Remove"
1904
2019
  }, "\xD7"));
1905
2020
  };
1906
2021
  if (isLoading) {
@@ -1936,7 +2051,7 @@ function RichTextEditor({
1936
2051
  e.preventDefault();
1937
2052
  e.stopPropagation();
1938
2053
  }
1939
- }, /*#__PURE__*/React.createElement("div", {
2054
+ }, !disabled && /*#__PURE__*/React.createElement("div", {
1940
2055
  className: "rte-toolbar"
1941
2056
  }, /*#__PURE__*/React.createElement("button", {
1942
2057
  type: "button",
@@ -1984,6 +2099,41 @@ function RichTextEditor({
1984
2099
  backgroundColor: '#e5e7eb',
1985
2100
  margin: '0 4px'
1986
2101
  }
2102
+ }), /*#__PURE__*/React.createElement("select", {
2103
+ value: currentBlockFormat,
2104
+ onMouseDown: e => e.stopPropagation(),
2105
+ onChange: e => {
2106
+ e.preventDefault();
2107
+ e.stopPropagation();
2108
+ applyBlockFormat(e.target.value);
2109
+ },
2110
+ className: "rte-toolbar-select rte-heading-select",
2111
+ title: "Text style"
2112
+ }, /*#__PURE__*/React.createElement("option", {
2113
+ value: "div"
2114
+ }, "Paragraph"), /*#__PURE__*/React.createElement("option", {
2115
+ value: "h1"
2116
+ }, "Heading 1"), /*#__PURE__*/React.createElement("option", {
2117
+ value: "h2"
2118
+ }, "Heading 2"), /*#__PURE__*/React.createElement("option", {
2119
+ value: "h3"
2120
+ }, "Heading 3"), /*#__PURE__*/React.createElement("option", {
2121
+ value: "blockquote"
2122
+ }, "Quote")), /*#__PURE__*/React.createElement("button", {
2123
+ type: "button",
2124
+ title: "Clear Formatting",
2125
+ className: "rte-toolbar-button rte-toolbar-button-text",
2126
+ onMouseDown: e => {
2127
+ e.preventDefault();
2128
+ clearFormatting();
2129
+ }
2130
+ }, "Tx"), /*#__PURE__*/React.createElement("div", {
2131
+ style: {
2132
+ width: '1px',
2133
+ height: '20px',
2134
+ backgroundColor: '#e5e7eb',
2135
+ margin: '0 4px'
2136
+ }
1987
2137
  }), /*#__PURE__*/React.createElement("select", {
1988
2138
  value: currentFontSize,
1989
2139
  onMouseDown: e => e.stopPropagation(),
@@ -2150,11 +2300,9 @@ function RichTextEditor({
2150
2300
  e.preventDefault();
2151
2301
  addLink();
2152
2302
  }
2153
- }, /*#__PURE__*/React.createElement("span", {
2154
- style: {
2155
- fontSize: '16px'
2156
- }
2157
- }, "\uD83D\uDD17")), /*#__PURE__*/React.createElement("input", {
2303
+ }, /*#__PURE__*/React.createElement(FaLink, {
2304
+ size: 14
2305
+ })), /*#__PURE__*/React.createElement("input", {
2158
2306
  ref: fileInputRef,
2159
2307
  type: "file",
2160
2308
  accept: "image/*",
@@ -2321,9 +2469,17 @@ function RichTextEditor({
2321
2469
  }
2322
2470
  return null;
2323
2471
  })()), /*#__PURE__*/React.createElement("div", {
2472
+ className: "rte-content-wrapper",
2473
+ style: {
2474
+ position: 'relative'
2475
+ }
2476
+ }, /*#__PURE__*/React.createElement("div", {
2324
2477
  ref: editorRef,
2325
2478
  contentEditable: editable && disabled !== true,
2326
2479
  suppressContentEditableWarning: true,
2480
+ role: "textbox",
2481
+ "aria-multiline": "true",
2482
+ "aria-label": label || "Rich text editor",
2327
2483
  onInput: handleInput,
2328
2484
  onPaste: handlePaste,
2329
2485
  onDrop: handleDrop,
@@ -2339,7 +2495,13 @@ function RichTextEditor({
2339
2495
  paddingLeft: paddingLeft || '12px'
2340
2496
  },
2341
2497
  className: `rte-content${editable ? " rte-is-editable" : ""}${editorFocused ? " rte-is-focused" : ""}`
2342
- }), renderImageToolbar(), /*#__PURE__*/React.createElement("div", {
2498
+ }), isEmpty && editable && disabled !== true && /*#__PURE__*/React.createElement("div", {
2499
+ className: "rte-placeholder",
2500
+ style: {
2501
+ left: paddingLeft || '12px'
2502
+ },
2503
+ "aria-hidden": "true"
2504
+ }, placeholder)), renderMediaToolbar(), /*#__PURE__*/React.createElement("div", {
2343
2505
  className: "rte-footer"
2344
2506
  }, /*#__PURE__*/React.createElement("div", {
2345
2507
  className: "rte-footer-content"
@@ -2355,13 +2517,11 @@ function RichTextEditor({
2355
2517
  }, /*#__PURE__*/React.createElement("div", {
2356
2518
  className: "rte-modal",
2357
2519
  onClick: e => e.stopPropagation()
2520
+ }, /*#__PURE__*/React.createElement("div", {
2521
+ className: "rte-modal-header"
2358
2522
  }, /*#__PURE__*/React.createElement("h3", {
2359
2523
  className: "rte-modal-title"
2360
- }, "Insert Link"), /*#__PURE__*/React.createElement("div", {
2361
- className: "rte-modal-divider"
2362
- }), /*#__PURE__*/React.createElement("div", {
2363
- className: "rte-modal-body"
2364
- }, /*#__PURE__*/React.createElement("div", {
2524
+ }, "Insert Link")), /*#__PURE__*/React.createElement("div", {
2365
2525
  className: "rte-form-group"
2366
2526
  }, /*#__PURE__*/React.createElement("label", {
2367
2527
  className: "rte-label"
@@ -2383,20 +2543,15 @@ function RichTextEditor({
2383
2543
  onChange: e => setLinkUrl(e.target.value),
2384
2544
  onKeyDown: e => e.key === 'Enter' && confirmLink(),
2385
2545
  autoFocus: true
2386
- }))), /*#__PURE__*/React.createElement("div", {
2387
- className: "rte-modal-divider",
2388
- style: {
2389
- margin: '8px 0 20px 0'
2390
- }
2391
- }), /*#__PURE__*/React.createElement("div", {
2392
- className: "rte-modal-footer"
2546
+ })), /*#__PURE__*/React.createElement("div", {
2547
+ className: "rte-modal-actions"
2393
2548
  }, /*#__PURE__*/React.createElement("button", {
2394
2549
  type: "button",
2395
- className: "rte-btn rte-btn-secondary",
2550
+ className: "rte-button rte-button-secondary",
2396
2551
  onClick: cancelLink
2397
2552
  }, "Cancel"), /*#__PURE__*/React.createElement("button", {
2398
2553
  type: "button",
2399
- className: "rte-btn rte-btn-primary",
2554
+ className: "rte-button rte-button-primary",
2400
2555
  onClick: confirmLink,
2401
2556
  disabled: !linkUrl
2402
2557
  }, "Insert")))), tableModalOpen && /*#__PURE__*/React.createElement("div", {