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.cjs.js CHANGED
@@ -233,6 +233,27 @@ const FaFont = ({
233
233
  }
234
234
  });
235
235
  };
236
+ const FaLink = ({
237
+ className,
238
+ size,
239
+ color,
240
+ style
241
+ }) => {
242
+ return /*#__PURE__*/React.createElement("span", {
243
+ className: className,
244
+ style: {
245
+ display: 'inline-flex',
246
+ alignItems: 'center',
247
+ justifyContent: 'center',
248
+ fontSize: size || '1em',
249
+ color: color || 'inherit',
250
+ ...style
251
+ },
252
+ dangerouslySetInnerHTML: {
253
+ __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>`
254
+ }
255
+ });
256
+ };
236
257
  const FaTable = ({
237
258
  className,
238
259
  size,
@@ -390,7 +411,7 @@ function styleInject(css, ref) {
390
411
  }
391
412
  }
392
413
 
393
- 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}";
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}";
394
415
  styleInject(css_248z);
395
416
 
396
417
  // Helper functions for HTML escaping
@@ -407,11 +428,12 @@ function RichTextEditor({
407
428
  showEditButton,
408
429
  onBlur,
409
430
  disabled = false,
410
- editable: initialEditable = false,
431
+ editable: initialEditable = true,
411
432
  value,
412
433
  isLoading,
413
434
  isList = false,
414
435
  label,
436
+ placeholder = "Type here...",
415
437
  showBorder = true,
416
438
  paddingLeft,
417
439
  minHeight,
@@ -448,6 +470,7 @@ function RichTextEditor({
448
470
  // NEW: Track current line height
449
471
  const [currentLineHeight, setCurrentLineHeight] = React.useState("");
450
472
  const [activeAlign, setActiveAlign] = React.useState(null);
473
+ const [currentBlockFormat, setCurrentBlockFormat] = React.useState("div");
451
474
  const [imageModalOpen, setImageModalOpen] = React.useState(false);
452
475
  const [selectedImageUrl, setSelectedImageUrl] = React.useState("");
453
476
  const [zoomLevel, setZoomLevel] = React.useState(1);
@@ -459,11 +482,12 @@ function RichTextEditor({
459
482
  const [tableRows, setTableRows] = React.useState(3);
460
483
  const [tableCols, setTableCols] = React.useState(3);
461
484
  const [selectionVersion, setSelectionVersion] = React.useState(0);
462
- const [selectedImage, setSelectedImage] = React.useState(null);
485
+ const [selectedMedia, setSelectedMedia] = React.useState(null);
463
486
  const [metrics, setMetrics] = React.useState({
464
487
  words: 0,
465
488
  chars: 0
466
489
  });
490
+ const [isEmpty, setIsEmpty] = React.useState(!value);
467
491
  const updateMetrics = React.useCallback(() => {
468
492
  if (!editorRef.current) return;
469
493
  // Calculate metrics immediately but outside of render path
@@ -475,6 +499,11 @@ function RichTextEditor({
475
499
  words,
476
500
  chars
477
501
  });
502
+
503
+ // Track emptiness for the placeholder. Account for media-only content.
504
+ const stripped = text.replace(/[\u200B\u00A0\s]/g, "");
505
+ const hasMedia = !!editorRef.current.querySelector("img, table, iframe");
506
+ setIsEmpty(stripped.length === 0 && !hasMedia);
478
507
  }, []);
479
508
  const openImageModal = url => {
480
509
  if (editorRef.current) {
@@ -539,11 +568,10 @@ function RichTextEditor({
539
568
  requestAnimationFrame(() => syncProcessedMediaRef.current(editorRef.current));
540
569
  }
541
570
  }, [value]);
542
-
543
- // Runs whenever editable changes (toggles delete icon visibility)
544
571
  React.useEffect(() => {
545
572
  if (!editable) {
546
573
  setEditorFocused(false);
574
+ clearMediaSelection();
547
575
  }
548
576
  syncProcessedMediaRef.current(editorRef.current);
549
577
  }, [editable]);
@@ -707,17 +735,15 @@ function RichTextEditor({
707
735
  return rgbToHex(computedColor);
708
736
  };
709
737
  const stripEditorChrome = root => {
710
- root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handles, .media-resize-handle").forEach(element => element.remove());
738
+ root.querySelectorAll(".image-delete-button, .video-delete-button, .video-edit-overlay, .media-resize-handle").forEach(element => element.remove());
739
+ root.querySelectorAll(".rte-media-selected").forEach(element => {
740
+ element.classList.remove("rte-media-selected");
741
+ });
711
742
  return root;
712
743
  };
713
- const getMediaSizeLimits = () => {
714
- const maxWidth = editorRef.current ? editorRef.current.getBoundingClientRect().width - 24 : 800;
715
- return {
716
- minWidth: 120,
717
- minHeight: 80,
718
- maxWidth,
719
- maxHeight: 720
720
- };
744
+ const getEditorInnerWidth = () => {
745
+ if (!editorRef.current) return 800;
746
+ return Math.max(editorRef.current.clientWidth - 24, 200);
721
747
  };
722
748
  const ensureImageMediaFrame = imageContainer => {
723
749
  if (!imageContainer) return null;
@@ -725,159 +751,163 @@ function RichTextEditor({
725
751
  if (frame) return frame;
726
752
  frame = document.createElement("div");
727
753
  frame.className = "image-media-frame";
728
- ["width", "height", "marginLeft", "marginTop", "maxWidth"].forEach(prop => {
729
- if (imageContainer.style[prop]) {
730
- frame.style[prop] = imageContainer.style[prop];
731
- imageContainer.style[prop] = "";
732
- }
733
- });
734
- if (imageContainer.dataset.explicitHeight) {
735
- frame.dataset.explicitHeight = imageContainer.dataset.explicitHeight;
736
- delete imageContainer.dataset.explicitHeight;
737
- }
738
754
  const children = Array.from(imageContainer.children);
739
755
  imageContainer.appendChild(frame);
740
756
  children.forEach(child => frame.appendChild(child));
741
757
  return frame;
742
758
  };
743
759
  const getImageMediaTarget = imageContainer => ensureImageMediaFrame(imageContainer) || imageContainer;
744
- const applyImageMediaSize = (frame, width, height, edge) => {
760
+ const getMediaWidthPercent = container => {
761
+ if (!container) return 100;
762
+ if (container.dataset.widthPercent) {
763
+ return Number(container.dataset.widthPercent);
764
+ }
765
+ const width = container.style.width || "";
766
+ if (width.endsWith("%")) {
767
+ return parseInt(width, 10) || 100;
768
+ }
769
+ const editorWidth = getEditorInnerWidth();
770
+ const rect = container.getBoundingClientRect();
771
+ if (editorWidth > 0 && rect.width > 0) {
772
+ return Math.round(rect.width / editorWidth * 100);
773
+ }
774
+ return 100;
775
+ };
776
+ const applyMediaWidthPercent = (container, percent) => {
777
+ if (!container) return;
778
+ const clamped = Math.max(25, Math.min(100, Math.round(percent)));
779
+ container.dataset.widthPercent = String(clamped);
780
+ container.style.width = `${clamped}%`;
781
+ container.style.maxWidth = "100%";
782
+ container.style.marginLeft = "";
783
+ container.style.marginTop = "";
784
+ if (container.classList.contains("video-container")) {
785
+ container.style.height = "0";
786
+ container.style.paddingBottom = "56.25%";
787
+ return;
788
+ }
789
+ const frame = getImageMediaTarget(container);
790
+ if (!frame) return;
791
+ frame.style.width = "100%";
792
+ frame.style.height = "";
745
793
  const img = frame.querySelector("img");
746
- const isVertical = edge === "top" || edge === "bottom";
747
- frame.style.width = `${Math.round(width)}px`;
748
- frame.style.maxWidth = "100%";
749
- if (isVertical) {
750
- frame.style.height = `${Math.round(height)}px`;
751
- frame.dataset.explicitHeight = "true";
752
- if (img) {
753
- img.style.width = "100%";
754
- img.style.height = "100%";
755
- img.style.objectFit = "contain";
756
- }
794
+ if (img) {
795
+ img.style.height = "auto";
796
+ img.style.objectFit = "";
797
+ }
798
+ };
799
+ const normalizeMediaWidth = container => {
800
+ if (!container) return;
801
+ if (container.classList.contains("image-small")) {
802
+ container.classList.remove("image-small");
803
+ applyMediaWidthPercent(container, 50);
804
+ return;
805
+ }
806
+ if (container.dataset.widthPercent) {
807
+ applyMediaWidthPercent(container, Number(container.dataset.widthPercent));
757
808
  return;
758
809
  }
759
- if (!frame.dataset.explicitHeight) {
760
- frame.style.height = "";
810
+ const width = container.style.width || "";
811
+ if (width.endsWith("%")) {
812
+ applyMediaWidthPercent(container, parseInt(width, 10) || 100);
813
+ return;
761
814
  }
762
- if (img) {
763
- img.style.width = "100%";
764
- if (frame.dataset.explicitHeight) {
765
- img.style.height = "100%";
766
- img.style.objectFit = "contain";
767
- } else {
768
- img.style.height = "auto";
769
- img.style.objectFit = "";
815
+ 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));
770
820
  }
821
+ return;
822
+ }
823
+ if (container.classList.contains("video-container")) {
824
+ applyMediaWidthPercent(container, 100);
771
825
  }
772
826
  };
773
- const applyVideoMediaSize = (container, width, height) => {
774
- container.style.paddingBottom = "0";
775
- container.style.width = `${Math.round(width)}px`;
776
- container.style.maxWidth = "100%";
777
- container.style.height = `${Math.round(height)}px`;
827
+ const clearMediaSelection = () => {
828
+ var _editorRef$current;
829
+ (_editorRef$current = editorRef.current) === null || _editorRef$current === void 0 || _editorRef$current.querySelectorAll(".rte-media-selected").forEach(element => {
830
+ element.classList.remove("rte-media-selected");
831
+ });
832
+ setSelectedMedia(null);
833
+ };
834
+ const selectMediaContainer = container => {
835
+ var _editorRef$current2;
836
+ if (!container || !((_editorRef$current2 = editorRef.current) !== null && _editorRef$current2 !== void 0 && _editorRef$current2.contains(container))) return;
837
+ editorRef.current.querySelectorAll(".rte-media-selected").forEach(element => {
838
+ element.classList.remove("rte-media-selected");
839
+ });
840
+ container.classList.add("rte-media-selected");
841
+ setSelectedMedia(container);
778
842
  };
779
843
  const attachMediaResizeHandle = container => {
780
- if (!container || container.querySelector(".media-resize-handles")) return;
781
- const isVideo = container.classList.contains("video-container");
782
- const resizeTarget = isVideo ? container : getImageMediaTarget(container);
844
+ var _resizeTarget$querySe;
845
+ if (!container) return;
846
+ const resizeTarget = container.classList.contains("video-container") ? container : getImageMediaTarget(container);
783
847
  if (!resizeTarget) return;
784
- const handlesWrapper = document.createElement("div");
785
- handlesWrapper.className = "media-resize-handles";
786
- handlesWrapper.setAttribute("contenteditable", "false");
787
- const edges = [{
788
- edge: "left",
789
- title: "Drag to resize width"
790
- }, {
791
- edge: "right",
792
- title: "Drag to resize width"
793
- }, {
794
- edge: "top",
795
- title: "Drag to resize height"
796
- }, {
797
- edge: "bottom",
798
- title: "Drag to resize height"
799
- }];
800
- edges.forEach(({
801
- edge,
802
- title
803
- }) => {
804
- const handle = document.createElement("div");
805
- handle.className = `media-resize-handle media-resize-handle-${edge}`;
806
- handle.title = title;
807
- handle.setAttribute("contenteditable", "false");
808
- handle.dataset.edge = edge;
809
- handle.addEventListener("mousedown", event => {
810
- if (!editable) return;
811
- event.preventDefault();
812
- event.stopPropagation();
813
- const limits = getMediaSizeLimits();
814
- const rect = resizeTarget.getBoundingClientRect();
815
- const startX = event.clientX;
816
- const startY = event.clientY;
817
- const startWidth = rect.width;
818
- const startHeight = rect.height;
819
- const startMarginLeft = Number.parseFloat(resizeTarget.style.marginLeft) || 0;
820
- const startMarginTop = Number.parseFloat(resizeTarget.style.marginTop) || 0;
821
- if (isVideo) {
822
- resizeTarget.style.paddingBottom = "0";
823
- }
824
- const onMouseMove = moveEvent => {
825
- const deltaX = moveEvent.clientX - startX;
826
- const deltaY = moveEvent.clientY - startY;
827
- let nextWidth = startWidth;
828
- let nextHeight = startHeight;
829
- if (edge === "right") {
830
- nextWidth = startWidth + deltaX;
831
- } else if (edge === "left") {
832
- nextWidth = startWidth - deltaX;
833
- } else if (edge === "bottom") {
834
- nextHeight = startHeight + deltaY;
835
- } else if (edge === "top") {
836
- nextHeight = startHeight - deltaY;
837
- }
838
- nextWidth = Math.max(limits.minWidth, Math.min(nextWidth, limits.maxWidth));
839
- nextHeight = Math.max(limits.minHeight, Math.min(nextHeight, limits.maxHeight));
840
- if (edge === "left") {
841
- resizeTarget.style.marginLeft = `${Math.round(startMarginLeft + (startWidth - nextWidth))}px`;
842
- }
843
- if (edge === "top") {
844
- resizeTarget.style.marginTop = `${Math.round(startMarginTop + (startHeight - nextHeight))}px`;
845
- }
846
- if (isVideo) {
847
- applyVideoMediaSize(resizeTarget, nextWidth, nextHeight);
848
- } else {
849
- applyImageMediaSize(resizeTarget, nextWidth, nextHeight, edge);
850
- }
851
- };
852
- const onMouseUp = () => {
853
- document.removeEventListener("mousemove", onMouseMove);
854
- document.removeEventListener("mouseup", onMouseUp);
855
- triggerChange();
856
- };
857
- document.addEventListener("mousemove", onMouseMove);
858
- document.addEventListener("mouseup", onMouseUp);
859
- });
860
- handlesWrapper.appendChild(handle);
848
+ (_resizeTarget$querySe = resizeTarget.querySelector(".media-resize-handle")) === null || _resizeTarget$querySe === void 0 || _resizeTarget$querySe.remove();
849
+ const handle = document.createElement("div");
850
+ handle.className = "media-resize-handle";
851
+ handle.title = "Drag to resize";
852
+ handle.setAttribute("contenteditable", "false");
853
+ handle.addEventListener("mousedown", event => {
854
+ if (!editable) return;
855
+ event.preventDefault();
856
+ event.stopPropagation();
857
+ selectMediaContainer(container);
858
+ const editorWidth = getEditorInnerWidth();
859
+ const startX = event.clientX;
860
+ const startWidth = container.getBoundingClientRect().width;
861
+ const onMouseMove = moveEvent => {
862
+ const nextWidth = Math.max(60, startWidth + (moveEvent.clientX - startX));
863
+ const percent = Math.round(nextWidth / editorWidth * 100);
864
+ applyMediaWidthPercent(container, percent);
865
+ };
866
+ const onMouseUp = () => {
867
+ document.removeEventListener("mousemove", onMouseMove);
868
+ document.removeEventListener("mouseup", onMouseUp);
869
+ triggerChange();
870
+ };
871
+ document.addEventListener("mousemove", onMouseMove);
872
+ document.addEventListener("mouseup", onMouseUp);
861
873
  });
862
- resizeTarget.appendChild(handlesWrapper);
874
+ resizeTarget.appendChild(handle);
863
875
  };
864
876
  const handleEditorFocus = () => {
865
877
  setEditorFocused(true);
866
878
  };
867
879
  const handleEditorBlur = () => {
868
880
  requestAnimationFrame(() => {
869
- var _editorRef$current;
870
- if (!((_editorRef$current = editorRef.current) !== null && _editorRef$current !== void 0 && _editorRef$current.contains(document.activeElement))) {
881
+ var _editorRef$current3;
882
+ if (!((_editorRef$current3 = editorRef.current) !== null && _editorRef$current3 !== void 0 && _editorRef$current3.contains(document.activeElement))) {
871
883
  setEditorFocused(false);
872
884
  }
873
885
  });
874
886
  };
875
887
  const updateMediaControlVisibility = container => {
876
- const handles = container.querySelector(".media-resize-handles");
877
- if (handles instanceof HTMLElement) {
878
- handles.style.display = editable ? "block" : "none";
879
- handles.style.pointerEvents = editable ? "auto" : "none";
888
+ const handle = container.querySelector(".media-resize-handle");
889
+ if (handle instanceof HTMLElement) {
890
+ handle.style.display = editable ? "block" : "none";
891
+ handle.style.pointerEvents = editable ? "auto" : "none";
892
+ }
893
+ };
894
+ const BLOCK_TAGS = ["P", "DIV", "H1", "H2", "H3", "BLOCKQUOTE", "LI"];
895
+ const getActiveBlock = node => {
896
+ if (!editorRef.current || !node) return null;
897
+ let current = node.nodeType === 3 ? node.parentNode : node;
898
+ while (current && current !== editorRef.current) {
899
+ if (current.nodeType === 1 && BLOCK_TAGS.includes(current.tagName)) {
900
+ return current;
901
+ }
902
+ current = current.parentNode;
880
903
  }
904
+ return editorRef.current;
905
+ };
906
+ const getBlockFormat = node => {
907
+ const block = getActiveBlock(node);
908
+ if (!block || block === editorRef.current) return "div";
909
+ const tag = block.tagName.toLowerCase();
910
+ return tag === "p" || tag === "li" ? "div" : tag;
881
911
  };
882
912
  const createMediaDeleteButton = (title, className, onRemove) => {
883
913
  const deleteBtn = document.createElement("button");
@@ -897,10 +927,10 @@ function RichTextEditor({
897
927
  // Listen for selection changes globally to update styles and list type in one pass
898
928
  React.useEffect(() => {
899
929
  const handleGlobalSelectionSync = () => {
900
- var _editorRef$current2;
930
+ var _editorRef$current4;
901
931
  // Only sync if the editor has focus
902
932
  const sel = window.getSelection();
903
- if (!sel || !sel.rangeCount || !((_editorRef$current2 = editorRef.current) !== null && _editorRef$current2 !== void 0 && _editorRef$current2.contains(sel.anchorNode))) {
933
+ if (!sel || !sel.rangeCount || !((_editorRef$current4 = editorRef.current) !== null && _editorRef$current4 !== void 0 && _editorRef$current4.contains(sel.anchorNode))) {
904
934
  return;
905
935
  }
906
936
 
@@ -948,6 +978,7 @@ function RichTextEditor({
948
978
  } else {
949
979
  setCurrentFontSize("16");
950
980
  }
981
+ setCurrentBlockFormat(getBlockFormat(sel.anchorNode));
951
982
  };
952
983
  document.addEventListener("selectionchange", handleGlobalSelectionSync);
953
984
  return () => {
@@ -1185,11 +1216,25 @@ function RichTextEditor({
1185
1216
  if (!videoContainer.querySelector(".video-delete-button")) {
1186
1217
  const deleteBtn = createMediaDeleteButton("Remove video", "video-delete-button image-delete-button", () => {
1187
1218
  videoContainer.remove();
1219
+ clearMediaSelection();
1188
1220
  triggerChange && triggerChange();
1189
1221
  });
1190
1222
  videoContainer.appendChild(deleteBtn);
1191
1223
  }
1224
+ if (!videoContainer.dataset.mediaEnhanced) {
1225
+ videoContainer.dataset.mediaEnhanced = "true";
1226
+ if (!videoContainer.classList.contains("image-align-left") && !videoContainer.classList.contains("image-align-center") && !videoContainer.classList.contains("image-align-right")) {
1227
+ videoContainer.classList.add("image-align-left");
1228
+ }
1229
+ videoContainer.addEventListener("click", event => {
1230
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1231
+ event.preventDefault();
1232
+ event.stopPropagation();
1233
+ selectMediaContainer(videoContainer);
1234
+ });
1235
+ }
1192
1236
  attachMediaResizeHandle(videoContainer);
1237
+ normalizeMediaWidth(videoContainer);
1193
1238
  updateMediaControlVisibility(videoContainer);
1194
1239
  });
1195
1240
  };
@@ -1209,6 +1254,7 @@ function RichTextEditor({
1209
1254
  }));
1210
1255
  }
1211
1256
  attachMediaResizeHandle(existingWrapper);
1257
+ normalizeMediaWidth(existingWrapper);
1212
1258
  updateMediaControlVisibility(existingWrapper);
1213
1259
  return;
1214
1260
  }
@@ -1218,24 +1264,21 @@ function RichTextEditor({
1218
1264
  wrapper.style.cursor = editable ? "pointer" : "default";
1219
1265
  const frame = document.createElement("div");
1220
1266
  frame.className = "image-media-frame";
1221
- if (img.getAttribute("width") && !frame.style.width) {
1222
- frame.style.width = `${img.getAttribute("width")}px`;
1223
- } else if (img.style.width && img.style.width.endsWith("px")) {
1224
- frame.style.width = img.style.width;
1225
- }
1226
1267
  img.classList.add("rte-image");
1227
1268
  img.setAttribute("data-align", align);
1228
- if (frame.style.width) {
1229
- img.style.width = "100%";
1230
- img.style.height = frame.dataset.explicitHeight ? "100%" : "auto";
1231
- } else {
1232
- img.style.width = "";
1233
- img.style.height = "auto";
1234
- }
1235
- img.addEventListener("click", event => {
1269
+ img.style.height = "auto";
1270
+ img.addEventListener("dblclick", event => {
1236
1271
  if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1272
+ event.preventDefault();
1273
+ event.stopPropagation();
1237
1274
  openImageModal(img.src);
1238
1275
  });
1276
+ img.addEventListener("click", event => {
1277
+ if (event.target.closest(".image-delete-button, .media-resize-handle")) return;
1278
+ event.preventDefault();
1279
+ event.stopPropagation();
1280
+ selectMediaContainer(wrapper);
1281
+ });
1239
1282
  const deleteBtn = createMediaDeleteButton("Remove image", "image-delete-button", () => {
1240
1283
  wrapper.remove();
1241
1284
  triggerChange && triggerChange();
@@ -1250,6 +1293,7 @@ function RichTextEditor({
1250
1293
  frame.appendChild(deleteBtn);
1251
1294
  wrapper.appendChild(frame);
1252
1295
  attachMediaResizeHandle(wrapper);
1296
+ normalizeMediaWidth(wrapper);
1253
1297
  if (nextSibling) {
1254
1298
  parentNode.insertBefore(wrapper, nextSibling);
1255
1299
  } else {
@@ -1276,9 +1320,19 @@ function RichTextEditor({
1276
1320
  const img = document.createElement('img');
1277
1321
  img.src = dataUrl;
1278
1322
  img.alt = fileName || "image";
1279
- img.addEventListener("click", () => openImageModal(dataUrl));
1323
+ img.addEventListener("dblclick", event => {
1324
+ event.preventDefault();
1325
+ event.stopPropagation();
1326
+ openImageModal(dataUrl);
1327
+ });
1328
+ img.addEventListener("click", event => {
1329
+ event.preventDefault();
1330
+ event.stopPropagation();
1331
+ selectMediaContainer(container);
1332
+ });
1280
1333
  frame.appendChild(img);
1281
1334
  container.appendChild(frame);
1335
+ applyMediaWidthPercent(container, 25);
1282
1336
 
1283
1337
  // Insert at cursor position
1284
1338
  insertNodeAtCursor(container);
@@ -1451,6 +1505,8 @@ function RichTextEditor({
1451
1505
  selection.addRange(newRange);
1452
1506
  };
1453
1507
  const handleKeyDown = React.useCallback(e => {
1508
+ if (applyMarkdownShortcut(e)) return;
1509
+
1454
1510
  // Handle Enter key
1455
1511
  if (e.key === 'Enter') {
1456
1512
  e.preventDefault();
@@ -1522,7 +1578,7 @@ function RichTextEditor({
1522
1578
  return;
1523
1579
  }
1524
1580
  if (e.key === "Backspace") {
1525
- var _node$closest, _node, _editorRef$current3;
1581
+ var _node$closest, _node, _editorRef$current5;
1526
1582
  const selection = window.getSelection();
1527
1583
  if (!(selection !== null && selection !== void 0 && selection.rangeCount)) return;
1528
1584
  const range = selection.getRangeAt(0);
@@ -1532,7 +1588,7 @@ function RichTextEditor({
1532
1588
  node = node.parentNode;
1533
1589
  }
1534
1590
  const listItem = (_node$closest = (_node = node).closest) === null || _node$closest === void 0 ? void 0 : _node$closest.call(_node, "li");
1535
- if (!listItem || !((_editorRef$current3 = editorRef.current) !== null && _editorRef$current3 !== void 0 && _editorRef$current3.contains(listItem))) return;
1591
+ if (!listItem || !((_editorRef$current5 = editorRef.current) !== null && _editorRef$current5 !== void 0 && _editorRef$current5.contains(listItem))) return;
1536
1592
  const list = listItem.parentNode;
1537
1593
  if (isListItemEffectivelyEmpty(listItem)) {
1538
1594
  e.preventDefault();
@@ -1609,6 +1665,65 @@ function RichTextEditor({
1609
1665
  const handleSelect = type => {
1610
1666
  exec(type === "unordered" ? "insertUnorderedList" : "insertOrderedList");
1611
1667
  };
1668
+ const applyBlockFormat = format => {
1669
+ document.execCommand("formatBlock", false, format);
1670
+ setCurrentBlockFormat(format);
1671
+ triggerChange();
1672
+ focus();
1673
+ };
1674
+ const clearFormatting = () => {
1675
+ document.execCommand("removeFormat", false, null);
1676
+ document.execCommand("unlink", false, null);
1677
+ document.execCommand("formatBlock", false, "div");
1678
+ setCurrentBlockFormat("div");
1679
+ setCurrentFontSize("16");
1680
+ setCurrentLineHeight("");
1681
+ setFontColor("#000000");
1682
+ triggerChange();
1683
+ focus();
1684
+ };
1685
+ const deleteTextBeforeCursorInBlock = (block, range, selection) => {
1686
+ const prefixRange = document.createRange();
1687
+ prefixRange.setStart(block, 0);
1688
+ prefixRange.setEnd(range.startContainer, range.startOffset);
1689
+ prefixRange.deleteContents();
1690
+ const nextRange = document.createRange();
1691
+ nextRange.setStart(block, 0);
1692
+ nextRange.collapse(true);
1693
+ selection.removeAllRanges();
1694
+ selection.addRange(nextRange);
1695
+ };
1696
+ const applyMarkdownShortcut = event => {
1697
+ if (event.key !== " " || event.ctrlKey || event.metaKey || event.altKey) {
1698
+ return false;
1699
+ }
1700
+ const selection = window.getSelection();
1701
+ if (!(selection !== null && selection !== void 0 && selection.rangeCount) || !selection.isCollapsed || !editorRef.current) {
1702
+ return false;
1703
+ }
1704
+ const range = selection.getRangeAt(0);
1705
+ const block = getActiveBlock(range.startContainer);
1706
+ if (!block || !editorRef.current.contains(block)) return false;
1707
+ const prefixRange = document.createRange();
1708
+ prefixRange.setStart(block, 0);
1709
+ prefixRange.setEnd(range.startContainer, range.startOffset);
1710
+ const textBeforeCursor = prefixRange.toString().replace(/\u00A0/g, " ").trim();
1711
+ const shortcuts = {
1712
+ "#": () => applyBlockFormat("h1"),
1713
+ "##": () => applyBlockFormat("h2"),
1714
+ "###": () => applyBlockFormat("h3"),
1715
+ ">": () => applyBlockFormat("blockquote"),
1716
+ "-": () => handleSelect("unordered"),
1717
+ "*": () => handleSelect("unordered"),
1718
+ "1.": () => handleSelect("ordered")
1719
+ };
1720
+ const action = shortcuts[textBeforeCursor];
1721
+ if (!action) return false;
1722
+ event.preventDefault();
1723
+ deleteTextBeforeCursorInBlock(block, range, selection);
1724
+ action();
1725
+ return true;
1726
+ };
1612
1727
  const onLineHeightChange = value => {
1613
1728
  if (!value) return;
1614
1729
  const sel = window.getSelection();
@@ -1790,6 +1905,7 @@ function RichTextEditor({
1790
1905
  }
1791
1906
  }, [disabled]);
1792
1907
  const handleEditorClick = React.useCallback(e => {
1908
+ var _editorRef$current6;
1793
1909
  setSelectionVersion(v => v + 1);
1794
1910
  const deleteBtn = e.target.closest('button[title="Remove image"], button[title="Remove video"]');
1795
1911
  if (deleteBtn && editable && editorFocused) {
@@ -1798,10 +1914,15 @@ function RichTextEditor({
1798
1914
  const wrapper = deleteBtn.closest('.image-container, .video-container');
1799
1915
  if (wrapper) {
1800
1916
  wrapper.remove();
1917
+ clearMediaSelection();
1801
1918
  triggerChange();
1802
1919
  }
1803
1920
  return;
1804
1921
  }
1922
+ 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) {
1924
+ return;
1925
+ }
1805
1926
 
1806
1927
  // Check if the click is on a link
1807
1928
  const clickedLink = e.target.closest('a');
@@ -1811,13 +1932,8 @@ function RichTextEditor({
1811
1932
  window.open(clickedLink.href, '_blank');
1812
1933
  return;
1813
1934
  }
1814
-
1815
- // NEW: Check if click is on an image for resizing
1816
- const clickedImg = e.target.closest('img');
1817
- if (clickedImg && !clickedImg.closest('.rte-modal')) {
1818
- setSelectedImage(clickedImg);
1819
- } else if (!e.target.closest('.rte-image-toolbar')) {
1820
- setSelectedImage(null);
1935
+ if (!e.target.closest('.rte-media-toolbar')) {
1936
+ clearMediaSelection();
1821
1937
  }
1822
1938
 
1823
1939
  // If disabled is true, prevent editing
@@ -1836,73 +1952,72 @@ function RichTextEditor({
1836
1952
  }, 0);
1837
1953
  }
1838
1954
  }, [editable, disabled, editorFocused, triggerChange]);
1839
- const renderImageToolbar = () => {
1840
- if (!selectedImage || !editorRef.current || !editable) return null;
1955
+ const renderMediaToolbar = () => {
1956
+ if (!selectedMedia || !editorRef.current || !editable) return null;
1841
1957
  const editorRect = editorRef.current.getBoundingClientRect();
1842
- const imgRect = selectedImage.getBoundingClientRect();
1843
- const top = imgRect.top - editorRect.top + editorRef.current.scrollTop;
1844
- const left = imgRect.left - editorRect.left + editorRef.current.scrollLeft;
1845
- const width = imgRect.width;
1958
+ 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;
1962
+ const currentPercent = getMediaWidthPercent(selectedMedia);
1963
+ const widthPresets = [25, 50, 75, 100];
1846
1964
  const handleAlignment = align => {
1847
- const wrapper = selectedImage.closest('.image-container');
1848
- if (wrapper) {
1849
- // Remove all alignment classes first
1850
- wrapper.classList.remove('image-align-left', 'image-align-center', 'image-align-right');
1851
- // Add the new alignment class
1852
- wrapper.classList.add(`image-align-${align}`);
1853
- selectedImage.setAttribute('data-align', align);
1854
- triggerChange();
1855
- }
1965
+ selectedMedia.classList.remove("image-align-left", "image-align-center", "image-align-right");
1966
+ selectedMedia.classList.add(`image-align-${align}`);
1967
+ const img = selectedMedia.querySelector("img");
1968
+ if (img) img.setAttribute("data-align", align);
1969
+ triggerChange();
1856
1970
  };
1857
- const removeImage = () => {
1858
- const wrapper = selectedImage.closest('.image-container');
1859
- if (wrapper) {
1860
- wrapper.remove();
1861
- setSelectedImage(null);
1862
- triggerChange();
1863
- }
1971
+ const setWidth = percent => {
1972
+ applyMediaWidthPercent(selectedMedia, percent);
1973
+ triggerChange();
1864
1974
  };
1865
- const toggleSize = () => {
1866
- const wrapper = selectedImage.closest('.image-container');
1867
- if (wrapper) {
1868
- const isSmall = wrapper.classList.contains('image-small');
1869
- if (isSmall) {
1870
- wrapper.classList.remove('image-small');
1871
- } else {
1872
- wrapper.classList.add('image-small');
1873
- }
1874
- triggerChange();
1975
+ const removeMedia = () => {
1976
+ selectedMedia.remove();
1977
+ clearMediaSelection();
1978
+ triggerChange();
1979
+ };
1980
+ const isActivePercent = percent => {
1981
+ if (!selectedMedia.dataset.widthPercent && !(selectedMedia.style.width || "").endsWith("%")) {
1982
+ return false;
1875
1983
  }
1984
+ return Math.abs(currentPercent - percent) <= 3;
1876
1985
  };
1877
1986
  return /*#__PURE__*/React.createElement("div", {
1878
- className: "rte-image-toolbar",
1987
+ className: "rte-media-toolbar",
1879
1988
  style: {
1880
- position: 'absolute',
1881
- top: Math.max(0, top - 45),
1882
- left: Math.max(0, left + width / 2 - 80),
1989
+ position: "absolute",
1990
+ top: Math.max(0, top - 44),
1991
+ left: Math.max(8, left + width / 2 - 156),
1883
1992
  zIndex: 1000
1884
1993
  }
1885
1994
  }, /*#__PURE__*/React.createElement("button", {
1886
1995
  type: "button",
1887
- onClick: () => handleAlignment('left'),
1996
+ onClick: () => handleAlignment("left"),
1888
1997
  title: "Align Left"
1889
1998
  }, "L"), /*#__PURE__*/React.createElement("button", {
1890
1999
  type: "button",
1891
- onClick: () => handleAlignment('center'),
2000
+ onClick: () => handleAlignment("center"),
1892
2001
  title: "Align Center"
1893
2002
  }, "C"), /*#__PURE__*/React.createElement("button", {
1894
2003
  type: "button",
1895
- onClick: () => handleAlignment('right'),
2004
+ onClick: () => handleAlignment("right"),
1896
2005
  title: "Align Right"
1897
- }, "R"), /*#__PURE__*/React.createElement("button", {
2006
+ }, "R"), /*#__PURE__*/React.createElement("span", {
2007
+ className: "rte-media-toolbar-divider"
2008
+ }), widthPresets.map(percent => /*#__PURE__*/React.createElement("button", {
2009
+ key: percent,
1898
2010
  type: "button",
1899
- onClick: toggleSize,
1900
- title: "Toggle 50% Width"
1901
- }, "50%"), /*#__PURE__*/React.createElement("button", {
2011
+ className: isActivePercent(percent) ? "active" : "",
2012
+ onClick: () => setWidth(percent),
2013
+ title: `${percent}% width`
2014
+ }, percent, "%")), /*#__PURE__*/React.createElement("span", {
2015
+ className: "rte-media-toolbar-divider"
2016
+ }), /*#__PURE__*/React.createElement("button", {
1902
2017
  type: "button",
1903
- onClick: removeImage,
2018
+ onClick: removeMedia,
1904
2019
  className: "danger",
1905
- title: "Remove Image"
2020
+ title: "Remove"
1906
2021
  }, "\xD7"));
1907
2022
  };
1908
2023
  if (isLoading) {
@@ -1938,7 +2053,7 @@ function RichTextEditor({
1938
2053
  e.preventDefault();
1939
2054
  e.stopPropagation();
1940
2055
  }
1941
- }, /*#__PURE__*/React.createElement("div", {
2056
+ }, !disabled && /*#__PURE__*/React.createElement("div", {
1942
2057
  className: "rte-toolbar"
1943
2058
  }, /*#__PURE__*/React.createElement("button", {
1944
2059
  type: "button",
@@ -1986,6 +2101,41 @@ function RichTextEditor({
1986
2101
  backgroundColor: '#e5e7eb',
1987
2102
  margin: '0 4px'
1988
2103
  }
2104
+ }), /*#__PURE__*/React.createElement("select", {
2105
+ value: currentBlockFormat,
2106
+ onMouseDown: e => e.stopPropagation(),
2107
+ onChange: e => {
2108
+ e.preventDefault();
2109
+ e.stopPropagation();
2110
+ applyBlockFormat(e.target.value);
2111
+ },
2112
+ className: "rte-toolbar-select rte-heading-select",
2113
+ title: "Text style"
2114
+ }, /*#__PURE__*/React.createElement("option", {
2115
+ value: "div"
2116
+ }, "Paragraph"), /*#__PURE__*/React.createElement("option", {
2117
+ value: "h1"
2118
+ }, "Heading 1"), /*#__PURE__*/React.createElement("option", {
2119
+ value: "h2"
2120
+ }, "Heading 2"), /*#__PURE__*/React.createElement("option", {
2121
+ value: "h3"
2122
+ }, "Heading 3"), /*#__PURE__*/React.createElement("option", {
2123
+ value: "blockquote"
2124
+ }, "Quote")), /*#__PURE__*/React.createElement("button", {
2125
+ type: "button",
2126
+ title: "Clear Formatting",
2127
+ className: "rte-toolbar-button rte-toolbar-button-text",
2128
+ onMouseDown: e => {
2129
+ e.preventDefault();
2130
+ clearFormatting();
2131
+ }
2132
+ }, "Tx"), /*#__PURE__*/React.createElement("div", {
2133
+ style: {
2134
+ width: '1px',
2135
+ height: '20px',
2136
+ backgroundColor: '#e5e7eb',
2137
+ margin: '0 4px'
2138
+ }
1989
2139
  }), /*#__PURE__*/React.createElement("select", {
1990
2140
  value: currentFontSize,
1991
2141
  onMouseDown: e => e.stopPropagation(),
@@ -2152,11 +2302,9 @@ function RichTextEditor({
2152
2302
  e.preventDefault();
2153
2303
  addLink();
2154
2304
  }
2155
- }, /*#__PURE__*/React.createElement("span", {
2156
- style: {
2157
- fontSize: '16px'
2158
- }
2159
- }, "\uD83D\uDD17")), /*#__PURE__*/React.createElement("input", {
2305
+ }, /*#__PURE__*/React.createElement(FaLink, {
2306
+ size: 14
2307
+ })), /*#__PURE__*/React.createElement("input", {
2160
2308
  ref: fileInputRef,
2161
2309
  type: "file",
2162
2310
  accept: "image/*",
@@ -2323,9 +2471,17 @@ function RichTextEditor({
2323
2471
  }
2324
2472
  return null;
2325
2473
  })()), /*#__PURE__*/React.createElement("div", {
2474
+ className: "rte-content-wrapper",
2475
+ style: {
2476
+ position: 'relative'
2477
+ }
2478
+ }, /*#__PURE__*/React.createElement("div", {
2326
2479
  ref: editorRef,
2327
2480
  contentEditable: editable && disabled !== true,
2328
2481
  suppressContentEditableWarning: true,
2482
+ role: "textbox",
2483
+ "aria-multiline": "true",
2484
+ "aria-label": label || "Rich text editor",
2329
2485
  onInput: handleInput,
2330
2486
  onPaste: handlePaste,
2331
2487
  onDrop: handleDrop,
@@ -2341,7 +2497,13 @@ function RichTextEditor({
2341
2497
  paddingLeft: paddingLeft || '12px'
2342
2498
  },
2343
2499
  className: `rte-content${editable ? " rte-is-editable" : ""}${editorFocused ? " rte-is-focused" : ""}`
2344
- }), renderImageToolbar(), /*#__PURE__*/React.createElement("div", {
2500
+ }), isEmpty && editable && disabled !== true && /*#__PURE__*/React.createElement("div", {
2501
+ className: "rte-placeholder",
2502
+ style: {
2503
+ left: paddingLeft || '12px'
2504
+ },
2505
+ "aria-hidden": "true"
2506
+ }, placeholder)), renderMediaToolbar(), /*#__PURE__*/React.createElement("div", {
2345
2507
  className: "rte-footer"
2346
2508
  }, /*#__PURE__*/React.createElement("div", {
2347
2509
  className: "rte-footer-content"
@@ -2357,13 +2519,11 @@ function RichTextEditor({
2357
2519
  }, /*#__PURE__*/React.createElement("div", {
2358
2520
  className: "rte-modal",
2359
2521
  onClick: e => e.stopPropagation()
2522
+ }, /*#__PURE__*/React.createElement("div", {
2523
+ className: "rte-modal-header"
2360
2524
  }, /*#__PURE__*/React.createElement("h3", {
2361
2525
  className: "rte-modal-title"
2362
- }, "Insert Link"), /*#__PURE__*/React.createElement("div", {
2363
- className: "rte-modal-divider"
2364
- }), /*#__PURE__*/React.createElement("div", {
2365
- className: "rte-modal-body"
2366
- }, /*#__PURE__*/React.createElement("div", {
2526
+ }, "Insert Link")), /*#__PURE__*/React.createElement("div", {
2367
2527
  className: "rte-form-group"
2368
2528
  }, /*#__PURE__*/React.createElement("label", {
2369
2529
  className: "rte-label"
@@ -2385,20 +2545,15 @@ function RichTextEditor({
2385
2545
  onChange: e => setLinkUrl(e.target.value),
2386
2546
  onKeyDown: e => e.key === 'Enter' && confirmLink(),
2387
2547
  autoFocus: true
2388
- }))), /*#__PURE__*/React.createElement("div", {
2389
- className: "rte-modal-divider",
2390
- style: {
2391
- margin: '8px 0 20px 0'
2392
- }
2393
- }), /*#__PURE__*/React.createElement("div", {
2394
- className: "rte-modal-footer"
2548
+ })), /*#__PURE__*/React.createElement("div", {
2549
+ className: "rte-modal-actions"
2395
2550
  }, /*#__PURE__*/React.createElement("button", {
2396
2551
  type: "button",
2397
- className: "rte-btn rte-btn-secondary",
2552
+ className: "rte-button rte-button-secondary",
2398
2553
  onClick: cancelLink
2399
2554
  }, "Cancel"), /*#__PURE__*/React.createElement("button", {
2400
2555
  type: "button",
2401
- className: "rte-btn rte-btn-primary",
2556
+ className: "rte-button rte-button-primary",
2402
2557
  onClick: confirmLink,
2403
2558
  disabled: !linkUrl
2404
2559
  }, "Insert")))), tableModalOpen && /*#__PURE__*/React.createElement("div", {