@rufous/ui 0.2.59 → 0.2.61

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/main.cjs CHANGED
@@ -165,6 +165,7 @@ __export(main_exports, {
165
165
  ViewIcon: () => viewIcon_default,
166
166
  WorkItemIcon: () => workItemIcon_default,
167
167
  Zoom: () => Zoom,
168
+ transformLegacyTodos: () => transformLegacyTodos,
168
169
  useRufousTheme: () => useRufousTheme
169
170
  });
170
171
  module.exports = __toCommonJS(main_exports);
@@ -9615,7 +9616,7 @@ var STATUS_COLORS = {
9615
9616
  };
9616
9617
  var CustomTaskItem = import_extension_task_item.default.extend({
9617
9618
  addStorage() {
9618
- return { taskTodoEnabled: true };
9619
+ return { taskTodoEnabled: false };
9619
9620
  },
9620
9621
  addAttributes() {
9621
9622
  return {
@@ -9661,25 +9662,7 @@ var CustomTaskItem = import_extension_task_item.default.extend({
9661
9662
  return false;
9662
9663
  }
9663
9664
  if (this.editor.storage.taskTodoEnabled === false) {
9664
- const afterContent2 = $from.parent.content.cut($from.parentOffset);
9665
- const schema2 = state.schema;
9666
- const tr2 = state.tr;
9667
- if (afterContent2.size > 0) {
9668
- tr2.delete($from.pos, $from.end());
9669
- }
9670
- let taskListDepth = -1;
9671
- for (let d = taskDepth - 1; d >= 0; d--) {
9672
- if ($from.node(d).type.name === "taskList") {
9673
- taskListDepth = d;
9674
- break;
9675
- }
9676
- }
9677
- const insertPos2 = taskListDepth !== -1 ? tr2.mapping.map($from.after(taskListDepth)) : tr2.mapping.map($from.after(taskDepth));
9678
- const newPara2 = afterContent2.size > 0 ? schema2.nodes.paragraph.create(null, afterContent2) : schema2.nodes.paragraph.create();
9679
- tr2.insert(insertPos2, newPara2);
9680
- tr2.setSelection(import_prosemirror_state.TextSelection.near(tr2.doc.resolve(insertPos2 + 1)));
9681
- this.editor.view.dispatch(tr2);
9682
- return true;
9665
+ return false;
9683
9666
  }
9684
9667
  const taskNode = $from.node(taskDepth);
9685
9668
  const status = taskNode.attrs.status || "todo";
@@ -9783,7 +9766,7 @@ var CustomTaskItem = import_extension_task_item.default.extend({
9783
9766
  addNodeView() {
9784
9767
  return ({ node, HTMLAttributes, getPos, editor }) => {
9785
9768
  const li = document.createElement("li");
9786
- li.style.cssText = "display: flex; align-items: flex-start; gap: 8px; margin: 4px 0; list-style: none; ";
9769
+ li.style.cssText = "display: flex; align-items: flex-start; gap: 6px; margin: 0; list-style: none; line-height: 1.7; ";
9787
9770
  Object.entries(HTMLAttributes || {}).forEach(([key, val]) => {
9788
9771
  if (val != null && val !== false) {
9789
9772
  li.setAttribute(key, String(val));
@@ -9792,7 +9775,7 @@ var CustomTaskItem = import_extension_task_item.default.extend({
9792
9775
  li.setAttribute("data-status", node.attrs.status || "todo");
9793
9776
  const label = document.createElement("label");
9794
9777
  label.contentEditable = "false";
9795
- label.style.cssText = "flex-shrink: 0; margin-top: 3px;";
9778
+ label.style.cssText = "flex-shrink: 0;";
9796
9779
  const checkbox = document.createElement("span");
9797
9780
  const updateCheckbox = (status) => {
9798
9781
  const colors = STATUS_COLORS[status] || STATUS_COLORS.todo;
@@ -9807,7 +9790,7 @@ var CustomTaskItem = import_extension_task_item.default.extend({
9807
9790
  "border-radius: 3px",
9808
9791
  "cursor: pointer",
9809
9792
  "user-select: none",
9810
- "margin-top: 7px",
9793
+ "margin-top: 3px",
9811
9794
  "border: 2px solid " + colors.border,
9812
9795
  `background-image: url("${imageUrl}")`,
9813
9796
  "background-repeat: no-repeat",
@@ -10033,34 +10016,42 @@ var Dropdown = ({ trigger, children, className = "", keepOpen = false }) => {
10033
10016
  const menuRef = (0, import_react55.useRef)(null);
10034
10017
  (0, import_react55.useEffect)(() => {
10035
10018
  const handleClick = (e) => {
10036
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
10019
+ const target = e.target;
10020
+ if (ref.current && !ref.current.contains(target) && menuRef.current && !menuRef.current.contains(target)) {
10021
+ setOpen(false);
10022
+ }
10037
10023
  };
10038
10024
  document.addEventListener("mousedown", handleClick);
10039
10025
  return () => document.removeEventListener("mousedown", handleClick);
10040
10026
  }, []);
10041
10027
  (0, import_react55.useEffect)(() => {
10042
- if (!open || !menuRef.current) return;
10028
+ if (!open || !menuRef.current || !ref.current) return;
10043
10029
  const menu = menuRef.current;
10044
- menu.style.left = "0";
10045
- menu.style.right = "auto";
10046
- requestAnimationFrame(() => {
10047
- if (!menu) return;
10048
- const rect = menu.getBoundingClientRect();
10030
+ const trigger2 = ref.current;
10031
+ const position = () => {
10032
+ const triggerRect = trigger2.getBoundingClientRect();
10049
10033
  const vw = window.innerWidth;
10050
- const parentLeft = ref.current?.getBoundingClientRect().left || 0;
10051
- if (rect.right > vw - 8) {
10052
- menu.style.left = "auto";
10053
- menu.style.right = "0";
10054
- const newRect = menu.getBoundingClientRect();
10055
- if (newRect.left < 8) {
10056
- menu.style.left = `${8 - parentLeft}px`;
10057
- menu.style.right = "auto";
10034
+ const vh = window.innerHeight;
10035
+ let left = triggerRect.left;
10036
+ let top = triggerRect.bottom + 4;
10037
+ menu.style.left = `${left}px`;
10038
+ menu.style.top = `${top}px`;
10039
+ requestAnimationFrame(() => {
10040
+ if (!menu) return;
10041
+ const menuRect = menu.getBoundingClientRect();
10042
+ if (menuRect.right > vw - 8) {
10043
+ left = Math.max(8, triggerRect.right - menuRect.width);
10058
10044
  }
10059
- } else if (rect.left < 8) {
10060
- menu.style.left = `${8 - parentLeft}px`;
10061
- menu.style.right = "auto";
10062
- }
10063
- });
10045
+ if (left < 8) left = 8;
10046
+ if (menuRect.bottom > vh - 8) {
10047
+ top = triggerRect.top - menuRect.height - 4;
10048
+ if (top < 8) top = 8;
10049
+ }
10050
+ menu.style.left = `${left}px`;
10051
+ menu.style.top = `${top}px`;
10052
+ });
10053
+ };
10054
+ position();
10064
10055
  }, [open]);
10065
10056
  return /* @__PURE__ */ import_react55.default.createElement("div", { className: `dropdown ${className}`, ref }, /* @__PURE__ */ import_react55.default.createElement(
10066
10057
  "button",
@@ -10071,7 +10062,7 @@ var Dropdown = ({ trigger, children, className = "", keepOpen = false }) => {
10071
10062
  },
10072
10063
  trigger.label,
10073
10064
  /* @__PURE__ */ import_react55.default.createElement("span", { className: "dropdown-arrow" }, "\u25BE")
10074
- ), open && /* @__PURE__ */ import_react55.default.createElement("div", { ref: menuRef, className: "dropdown-menu", onClick: keepOpen ? void 0 : () => setOpen(false) }, typeof children === "function" ? children(() => setOpen(false)) : children));
10065
+ ), open && /* @__PURE__ */ import_react55.default.createElement("div", { ref: menuRef, className: "dropdown-menu dropdown-menu-fixed", onClick: keepOpen ? void 0 : () => setOpen(false) }, typeof children === "function" ? children(() => setOpen(false)) : children));
10075
10066
  };
10076
10067
  var InsertPanel = ({ editor, onClose, mode = "video" }) => {
10077
10068
  const [activeTab, setActiveTab] = (0, import_react55.useState)("link");
@@ -11489,6 +11480,79 @@ var VideoToolbar = ({ editor }) => {
11489
11480
  };
11490
11481
  var VideoToolbar_default = VideoToolbar;
11491
11482
 
11483
+ // lib/RufousTextEditor/legacyTodoTransform.ts
11484
+ var IMAGE_TO_STATUS = {
11485
+ "todo-blank.svg": "todo",
11486
+ "working.svg": "working",
11487
+ "blocked.svg": "blocked",
11488
+ "closed.svg": "resolved"
11489
+ };
11490
+ function getStatusFromImgSrc(src) {
11491
+ for (const [key, status] of Object.entries(IMAGE_TO_STATUS)) {
11492
+ if (src.includes(key)) return status;
11493
+ }
11494
+ return "todo";
11495
+ }
11496
+ function transformLegacyTodos(html) {
11497
+ if (!html || !html.includes("todo-item")) return html;
11498
+ const div = document.createElement("div");
11499
+ div.innerHTML = html;
11500
+ const children = Array.from(div.childNodes);
11501
+ const result = [];
11502
+ let currentTaskList = null;
11503
+ for (const child of children) {
11504
+ if (child instanceof HTMLElement && child.classList.contains("todo-item")) {
11505
+ const img = child.querySelector("button img, .todo-item-button img");
11506
+ const status = img ? getStatusFromImgSrc(img.src) : "todo";
11507
+ let text = "";
11508
+ const classSpan = child.querySelector(".todo-item-text");
11509
+ if (classSpan) {
11510
+ text = classSpan.textContent?.trim() || "";
11511
+ } else {
11512
+ const spans = child.querySelectorAll("span");
11513
+ for (const span of Array.from(spans)) {
11514
+ if (!span.closest("button") && !span.closest(".todo-item-button")) {
11515
+ text = span.textContent?.trim() || "";
11516
+ break;
11517
+ }
11518
+ }
11519
+ }
11520
+ const li = document.createElement("li");
11521
+ li.setAttribute("data-type", "taskItem");
11522
+ li.setAttribute("data-status", status);
11523
+ li.setAttribute("data-checked", "false");
11524
+ const label = document.createElement("label");
11525
+ const checkbox = document.createElement("input");
11526
+ checkbox.type = "checkbox";
11527
+ const labelSpan = document.createElement("span");
11528
+ label.appendChild(checkbox);
11529
+ label.appendChild(labelSpan);
11530
+ const contentDiv = document.createElement("div");
11531
+ const p = document.createElement("p");
11532
+ if (text) {
11533
+ p.textContent = text;
11534
+ }
11535
+ contentDiv.appendChild(p);
11536
+ li.appendChild(label);
11537
+ li.appendChild(contentDiv);
11538
+ if (!currentTaskList) {
11539
+ currentTaskList = document.createElement("ul");
11540
+ currentTaskList.setAttribute("data-type", "taskList");
11541
+ result.push(currentTaskList);
11542
+ }
11543
+ currentTaskList.appendChild(li);
11544
+ } else {
11545
+ currentTaskList = null;
11546
+ result.push(child);
11547
+ }
11548
+ }
11549
+ const output = document.createElement("div");
11550
+ for (const node of result) {
11551
+ output.appendChild(node);
11552
+ }
11553
+ return output.innerHTML;
11554
+ }
11555
+
11492
11556
  // lib/RufousTextEditor/RufousTextEditor.tsx
11493
11557
  var VARIANT_BUTTONS = {
11494
11558
  default: [
@@ -11564,6 +11628,9 @@ var RufousTextEditor = ({
11564
11628
  variant = "default",
11565
11629
  buttons,
11566
11630
  hideButtons,
11631
+ disabled = false,
11632
+ error = false,
11633
+ helperText,
11567
11634
  width,
11568
11635
  height,
11569
11636
  resizable = false,
@@ -11589,8 +11656,9 @@ var RufousTextEditor = ({
11589
11656
  (0, import_react58.useEffect)(() => {
11590
11657
  onBlurRef.current = onBlur;
11591
11658
  }, [onBlur]);
11659
+ const isEditable = editable && !disabled;
11592
11660
  const editor = (0, import_react59.useEditor)({
11593
- editable,
11661
+ editable: isEditable,
11594
11662
  extensions: [
11595
11663
  import_starter_kit.default,
11596
11664
  import_extension_placeholder.default.configure({
@@ -11679,19 +11747,28 @@ var RufousTextEditor = ({
11679
11747
  return false;
11680
11748
  }
11681
11749
  },
11682
- content: initialContent || "",
11750
+ content: transformLegacyTodos(initialContent || ""),
11683
11751
  onUpdate: ({ editor: e }) => {
11684
11752
  onChangeRef.current?.(e.getHTML(), e.getJSON());
11685
11753
  }
11686
11754
  });
11755
+ const wrapperRef = (0, import_react58.useRef)(null);
11687
11756
  (0, import_react58.useEffect)(() => {
11688
11757
  if (!editor) return;
11689
- const handler = () => {
11690
- onBlurRef.current?.(editor.getHTML(), editor.getJSON());
11758
+ let blurTimer = null;
11759
+ const handler = ({ event }) => {
11760
+ const relatedTarget = event?.relatedTarget;
11761
+ if (relatedTarget && wrapperRef.current?.contains(relatedTarget)) return;
11762
+ if (relatedTarget?.closest?.(".rf-rte-mention-dropdown, .tippy-box, .link-modal-overlay, .ai-modal-overlay, .modal-overlay, .rf-spell-tooltip")) return;
11763
+ if (blurTimer) clearTimeout(blurTimer);
11764
+ blurTimer = setTimeout(() => {
11765
+ onBlurRef.current?.(editor.getHTML(), editor.getJSON());
11766
+ }, 100);
11691
11767
  };
11692
11768
  editor.on("blur", handler);
11693
11769
  return () => {
11694
11770
  editor.off("blur", handler);
11771
+ if (blurTimer) clearTimeout(blurTimer);
11695
11772
  };
11696
11773
  }, [editor]);
11697
11774
  const setLinkRef = (0, import_react58.useRef)(null);
@@ -11842,7 +11919,8 @@ var RufousTextEditor = ({
11842
11919
  return /* @__PURE__ */ import_react58.default.createElement(
11843
11920
  "div",
11844
11921
  {
11845
- className: `rf-rte-wrapper editor-wrapper ${resizable ? "rf-rte-resizable" : ""} ${sxClass} ${className || ""}`,
11922
+ ref: wrapperRef,
11923
+ className: `rf-rte-wrapper editor-wrapper ${resizable ? "rf-rte-resizable" : ""} ${disabled ? "rf-rte-disabled" : ""} ${error ? "rf-rte-error" : ""} ${sxClass} ${className || ""}`,
11846
11924
  style: {
11847
11925
  ...style,
11848
11926
  ...width ? { width: typeof width === "number" ? `${width}px` : width } : {},
@@ -12017,17 +12095,19 @@ var RufousTextEditor = ({
12017
12095
  }
12018
12096
  ), "No follow"))), /* @__PURE__ */ import_react58.default.createElement("div", { className: "link-modal-footer" }, /* @__PURE__ */ import_react58.default.createElement("button", { className: "link-modal-btn-unlink", onClick: handleLinkRemove }, "Unlink"), /* @__PURE__ */ import_react58.default.createElement("button", { className: "link-modal-btn-apply", onClick: handleLinkSubmit }, "Update")))),
12019
12097
  document.body
12020
- ))
12098
+ )),
12099
+ helperText && /* @__PURE__ */ import_react58.default.createElement("div", { className: `rf-rte-helper-text ${error ? "rf-rte-helper-error" : ""}` }, helperText)
12021
12100
  );
12022
12101
  };
12023
12102
  var RufousTextContent = ({ content, className, style, sx }) => {
12024
12103
  const sxClass = useSx(sx);
12104
+ const transformedContent = (0, import_react58.useMemo)(() => transformLegacyTodos(content || ""), [content]);
12025
12105
  return /* @__PURE__ */ import_react58.default.createElement(
12026
12106
  "div",
12027
12107
  {
12028
12108
  className: `rf-rte-content ${sxClass} ${className || ""}`,
12029
12109
  style,
12030
- dangerouslySetInnerHTML: { __html: content }
12110
+ dangerouslySetInnerHTML: { __html: transformedContent }
12031
12111
  }
12032
12112
  );
12033
12113
  };
@@ -12169,5 +12249,6 @@ var RufousTextContent = ({ content, className, style, sx }) => {
12169
12249
  ViewIcon,
12170
12250
  WorkItemIcon,
12171
12251
  Zoom,
12252
+ transformLegacyTodos,
12172
12253
  useRufousTheme
12173
12254
  });
package/dist/main.css CHANGED
@@ -72,7 +72,7 @@
72
72
  display: flex;
73
73
  justify-content: center;
74
74
  align-items: center;
75
- z-index: 1000;
75
+ z-index: 99999;
76
76
  backdrop-filter: blur(2px);
77
77
  }
78
78
  .dialog-container {
@@ -6595,11 +6595,37 @@ pre {
6595
6595
  }
6596
6596
  .rf-rte-wrapper.rf-rte-resizable {
6597
6597
  resize: vertical;
6598
- overflow: visible;
6598
+ overflow: hidden;
6599
6599
  min-height: 200px;
6600
6600
  }
6601
6601
  .rf-rte-wrapper.rf-rte-resizable .editor-content-wrapper {
6602
6602
  overflow: auto;
6603
+ flex: 1;
6604
+ min-height: 0;
6605
+ }
6606
+ .rf-rte-wrapper.rf-rte-disabled {
6607
+ opacity: 0.6;
6608
+ pointer-events: none;
6609
+ user-select: none;
6610
+ }
6611
+ .rf-rte-wrapper.rf-rte-disabled .toolbar {
6612
+ opacity: 0.5;
6613
+ }
6614
+ .rf-rte-wrapper.rf-rte-error {
6615
+ border-color: #dc2626;
6616
+ }
6617
+ .rf-rte-wrapper.rf-rte-error:focus-within {
6618
+ border-color: #dc2626;
6619
+ box-shadow: 0 0 0 2px rgba(220, 38, 38, 0.1);
6620
+ }
6621
+ .rf-rte-helper-text {
6622
+ font-size: 0.75rem;
6623
+ color: var(--text-secondary, #6b7280);
6624
+ margin-top: 4px;
6625
+ padding: 0 4px;
6626
+ }
6627
+ .rf-rte-helper-text.rf-rte-helper-error {
6628
+ color: #dc2626;
6603
6629
  }
6604
6630
  .rf-rte-wrapper:focus-within,
6605
6631
  .rf-rte-wrapper:has(.dropdown-menu),
@@ -6615,6 +6641,8 @@ pre {
6615
6641
  left: 0;
6616
6642
  right: 0;
6617
6643
  bottom: 0;
6644
+ width: 100% !important;
6645
+ height: 100% !important;
6618
6646
  z-index: 99999;
6619
6647
  border-radius: 0;
6620
6648
  margin: 0;
@@ -6729,11 +6757,13 @@ pre {
6729
6757
  margin-left: 2px;
6730
6758
  opacity: 0.5;
6731
6759
  }
6760
+ .rf-rte-wrapper .dropdown-menu.dropdown-menu-fixed {
6761
+ position: fixed;
6762
+ top: auto;
6763
+ left: auto;
6764
+ z-index: 99999;
6765
+ }
6732
6766
  .rf-rte-wrapper .dropdown-menu {
6733
- position: absolute;
6734
- top: 100%;
6735
- left: 0;
6736
- z-index: 1000;
6737
6767
  min-width: 170px;
6738
6768
  padding: 4px;
6739
6769
  background: #fff;
@@ -7236,7 +7266,7 @@ pre {
7236
7266
  letter-spacing: 0.05em;
7237
7267
  }
7238
7268
  .rf-rte-wrapper .tiptap p {
7239
- margin: 0.5em 0;
7269
+ margin: 1px 0;
7240
7270
  }
7241
7271
  .rf-rte-wrapper .tiptap ul,
7242
7272
  .rf-rte-wrapper .tiptap ol {
@@ -7254,12 +7284,21 @@ pre {
7254
7284
  display: flex;
7255
7285
  align-items: flex-start;
7256
7286
  gap: 8px;
7257
- margin: 4px 0;
7287
+ margin: 0;
7288
+ }
7289
+ .rf-rte-wrapper .tiptap ul[data-type=taskList] li.task-item p {
7290
+ margin: 0;
7258
7291
  }
7259
7292
  .rf-rte-wrapper .tiptap ul[data-type=taskList] li.task-item > label {
7260
7293
  flex-shrink: 0;
7261
7294
  margin-top: 3px;
7262
7295
  }
7296
+ .rf-rte-wrapper .tiptap ul[data-type=taskList] li {
7297
+ line-height: 1.7 !important;
7298
+ }
7299
+ .rf-rte-wrapper .tiptap ul[data-type=taskList] li p[style*=line-height] {
7300
+ line-height: inherit !important;
7301
+ }
7263
7302
  .rf-rte-wrapper .task-checkbox {
7264
7303
  display: inline-flex;
7265
7304
  align-items: center;
@@ -9073,14 +9112,18 @@ pre {
9073
9112
  }
9074
9113
  .rf-rte-content ul[data-type=taskList] {
9075
9114
  list-style: none;
9076
- padding-left: 8px;
9115
+ padding-left: 24px;
9077
9116
  }
9078
9117
  .rf-rte-content ul[data-type=taskList] li {
9079
9118
  display: flex;
9080
9119
  align-items: flex-start;
9081
- gap: 8px;
9082
- margin: 8px 0;
9120
+ gap: 6px;
9121
+ margin: 0;
9083
9122
  list-style: none;
9123
+ line-height: 1.7;
9124
+ }
9125
+ .rf-rte-content ul[data-type=taskList] li p {
9126
+ margin: 0;
9084
9127
  }
9085
9128
  .rf-rte-content ul[data-type=taskList] li::before {
9086
9129
  content: "";
@@ -9088,7 +9131,7 @@ pre {
9088
9131
  display: inline-block;
9089
9132
  width: 18px;
9090
9133
  height: 18px;
9091
- margin-top: 4px;
9134
+ margin-top: 3px;
9092
9135
  border-radius: 3px;
9093
9136
  border: 2px solid #dc2626;
9094
9137
  background-repeat: no-repeat;
package/dist/main.d.cts CHANGED
@@ -1646,6 +1646,9 @@ interface RufousTextEditorProps {
1646
1646
  variant?: EditorVariant;
1647
1647
  buttons?: ToolbarButton[];
1648
1648
  hideButtons?: ToolbarButton[];
1649
+ disabled?: boolean;
1650
+ error?: boolean;
1651
+ helperText?: string;
1649
1652
  width?: number | string;
1650
1653
  height?: number | string;
1651
1654
  resizable?: boolean;
@@ -1662,6 +1665,42 @@ interface RufousTextContentProps {
1662
1665
  }
1663
1666
  declare const RufousTextContent: React__default.FC<RufousTextContentProps>;
1664
1667
 
1668
+ /**
1669
+ * Transforms legacy Jodit-style todo HTML into TipTap-compatible taskList HTML.
1670
+ *
1671
+ * Old Jodit format (two variants):
1672
+ *
1673
+ * Variant 1 (class-based):
1674
+ * <p class="todo-item">
1675
+ * <button class="todo-item-button" contenteditable="false">
1676
+ * <img src="...todo-blank.svg" alt="icon">
1677
+ * </button>
1678
+ * <span class="todo-item-text"> Todo text</span>
1679
+ * </p>
1680
+ *
1681
+ * Variant 2 (inline-styled):
1682
+ * <p class="todo-item" style="display: flex; ...">
1683
+ * <button class="todo-item-button" contenteditable="false" style="...">
1684
+ * <img src="...todo-blank.svg" alt="icon" style="...">
1685
+ * </button>
1686
+ * <span style="margin-top: -2px; flex-grow: 1;"> Todo text</span>
1687
+ * </p>
1688
+ *
1689
+ * New TipTap format:
1690
+ * <ul data-type="taskList">
1691
+ * <li data-status="todo">
1692
+ * <label><input type="checkbox"></label>
1693
+ * <div><p>Todo text</p></div>
1694
+ * </li>
1695
+ * </ul>
1696
+ */
1697
+ /**
1698
+ * Transforms legacy Jodit todo HTML to TipTap taskList format.
1699
+ * Non-todo content is passed through unchanged.
1700
+ * Consecutive todo items are grouped into a single <ul data-type="taskList">.
1701
+ */
1702
+ declare function transformLegacyTodos(html: string): string;
1703
+
1665
1704
  interface MentionItemData {
1666
1705
  id: string;
1667
1706
  name: string;
@@ -1669,4 +1708,4 @@ interface MentionItemData {
1669
1708
  shortName?: string;
1670
1709
  }
1671
1710
 
1672
- export { APP_THEMES, Accordion, AccordionDetails, type AccordionDetailsProps, type AccordionProps, AccordionSummary, type AccordionSummaryProps, type Action, ActivateUserIcon, AddButton, AddressLookup, ArchivedIcon, AssignGroupIcon, Autocomplete, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BaseDialog, Box, type BoxProps, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, CameraIcon, CancelButton, Card, CardActions, type CardActionsProps, CardContent, type CardContentProps, CardHeader, type CardHeaderProps, CardMedia, type CardMediaProps, type CardProps, Checkbox, type CheckboxProps, Chip, type ChipProps, CircularProgress, CircularProgressIcon, type CircularProgressIconProps, CloseIcon, Collapse, type CollapseProps, type Column, CopyIcon, DataGrid, type DataGridProps, DateField, type DateFieldProps, type DateFormatString, DateRangeField, type DateRangeFieldProps, type DateRangeValue, DifficultyAllIcon, DifficultyEasyIcon, DifficultyHardIcon, DifficultyMediumIcon, Divider, type DividerProps, DollarIcon, DownloadIcon, DownloadPdfIcon, Drawer, type DrawerProps, EditChatIcon, EditIcon, EngagementIcon, Fade, type FadeProps, FunctionIcon, Grid, type GridProps, Grow, type GrowProps, HelpOutlinedIcon, HierarchyIcon, IconButton, type IconButtonProps, ImageField, type ImageFieldProps, InactiveGroupIcon, IndustryIcon, InvoiceIcon, Link, type LinkProps, List, ListItem, ListItemButton, type ListItemButtonProps, ListItemIcon, type ListItemIconProps, type ListItemProps, ListItemText, type ListItemTextProps, type ListProps, ListSubheader, type ListSubheaderProps, LocationPinIcon, LogsIcon, Menu, MenuDivider, MenuItem, type MenuItemProps, MenuList, type MenuListProps, type MenuProps, MinExperienceIcon, NineDotMenuIcon, NotificationIcon, Paper, type PaperProps, PhoneField, type PhoneFieldProps, Popover, type PopoverProps, Popper, type PopperProps, ProjectIcon, QualificationsIcon, QuestionStatusAllIcon, QuestionStatusPrivateIcon, QuestionStatusPublicIcon, QuestionTypeAllIcon, QuestionTypeCodingIcon, QuestionTypeDescriptiveIcon, QuestionTypeMultipleIcon, QuestionTypeSingleIcon, Radio, RadioGroup, type RadioGroupProps, type RadioProps, Rating, type RatingProps, RefreshIcon, ResendInviteIcon, RolesIcon, RufousAiIcon, RufousBirdIcon, RufousLauncherIcon, RufousLogoLoader, type RufousLogoLoaderProps, RufousTextContent, type RufousTextContentProps, RufousTextEditor, type MentionItemData as RufousTextEditorMentionItem, type RufousTextEditorProps, RufousThemeProvider, Select, type SelectProps, SidebarIcon, Skeleton, type SkeletonProps, Slide, type SlideProps, Slider, type SliderProps, Snackbar, type SnackbarProps, SoftSkillsIcon, type SortDirection, Stack, type StackProps, StandardButton, Step, StepButton, type StepButtonProps, StepContent, type StepContentProps, StepLabel, type StepLabelProps, type StepProps, Stepper, type StepperProps, SubmitButton, SubscribeIcon, SuspendUserIcon, Switch, type SwitchProps, type SxProp, Tab, TabPanel, type TabPanelProps, type TabProps, Tabs, type TabsProps, TechnicalSkillsIcon, TextField, type TextFieldProps, TickIcon, TimerIcon, ToggleButton, ToggleButtonGroup, type ToggleButtonGroupProps, type ToggleButtonProps, Tooltip, type TooltipProps, TrashIcon, Typography, type TypographyProps, UnArchivedIcon, UnsubscribeIcon, UploadIcon, UserAssignIcon, ViewIcon, WorkItemIcon, Zoom, type ZoomProps, useRufousTheme };
1711
+ export { APP_THEMES, Accordion, AccordionDetails, type AccordionDetailsProps, type AccordionProps, AccordionSummary, type AccordionSummaryProps, type Action, ActivateUserIcon, AddButton, AddressLookup, ArchivedIcon, AssignGroupIcon, Autocomplete, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BaseDialog, Box, type BoxProps, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, CameraIcon, CancelButton, Card, CardActions, type CardActionsProps, CardContent, type CardContentProps, CardHeader, type CardHeaderProps, CardMedia, type CardMediaProps, type CardProps, Checkbox, type CheckboxProps, Chip, type ChipProps, CircularProgress, CircularProgressIcon, type CircularProgressIconProps, CloseIcon, Collapse, type CollapseProps, type Column, CopyIcon, DataGrid, type DataGridProps, DateField, type DateFieldProps, type DateFormatString, DateRangeField, type DateRangeFieldProps, type DateRangeValue, DifficultyAllIcon, DifficultyEasyIcon, DifficultyHardIcon, DifficultyMediumIcon, Divider, type DividerProps, DollarIcon, DownloadIcon, DownloadPdfIcon, Drawer, type DrawerProps, EditChatIcon, EditIcon, EngagementIcon, Fade, type FadeProps, FunctionIcon, Grid, type GridProps, Grow, type GrowProps, HelpOutlinedIcon, HierarchyIcon, IconButton, type IconButtonProps, ImageField, type ImageFieldProps, InactiveGroupIcon, IndustryIcon, InvoiceIcon, Link, type LinkProps, List, ListItem, ListItemButton, type ListItemButtonProps, ListItemIcon, type ListItemIconProps, type ListItemProps, ListItemText, type ListItemTextProps, type ListProps, ListSubheader, type ListSubheaderProps, LocationPinIcon, LogsIcon, Menu, MenuDivider, MenuItem, type MenuItemProps, MenuList, type MenuListProps, type MenuProps, MinExperienceIcon, NineDotMenuIcon, NotificationIcon, Paper, type PaperProps, PhoneField, type PhoneFieldProps, Popover, type PopoverProps, Popper, type PopperProps, ProjectIcon, QualificationsIcon, QuestionStatusAllIcon, QuestionStatusPrivateIcon, QuestionStatusPublicIcon, QuestionTypeAllIcon, QuestionTypeCodingIcon, QuestionTypeDescriptiveIcon, QuestionTypeMultipleIcon, QuestionTypeSingleIcon, Radio, RadioGroup, type RadioGroupProps, type RadioProps, Rating, type RatingProps, RefreshIcon, ResendInviteIcon, RolesIcon, RufousAiIcon, RufousBirdIcon, RufousLauncherIcon, RufousLogoLoader, type RufousLogoLoaderProps, RufousTextContent, type RufousTextContentProps, RufousTextEditor, type MentionItemData as RufousTextEditorMentionItem, type RufousTextEditorProps, RufousThemeProvider, Select, type SelectProps, SidebarIcon, Skeleton, type SkeletonProps, Slide, type SlideProps, Slider, type SliderProps, Snackbar, type SnackbarProps, SoftSkillsIcon, type SortDirection, Stack, type StackProps, StandardButton, Step, StepButton, type StepButtonProps, StepContent, type StepContentProps, StepLabel, type StepLabelProps, type StepProps, Stepper, type StepperProps, SubmitButton, SubscribeIcon, SuspendUserIcon, Switch, type SwitchProps, type SxProp, Tab, TabPanel, type TabPanelProps, type TabProps, Tabs, type TabsProps, TechnicalSkillsIcon, TextField, type TextFieldProps, TickIcon, TimerIcon, ToggleButton, ToggleButtonGroup, type ToggleButtonGroupProps, type ToggleButtonProps, Tooltip, type TooltipProps, TrashIcon, Typography, type TypographyProps, UnArchivedIcon, UnsubscribeIcon, UploadIcon, UserAssignIcon, ViewIcon, WorkItemIcon, Zoom, type ZoomProps, transformLegacyTodos, useRufousTheme };
package/dist/main.d.ts CHANGED
@@ -1646,6 +1646,9 @@ interface RufousTextEditorProps {
1646
1646
  variant?: EditorVariant;
1647
1647
  buttons?: ToolbarButton[];
1648
1648
  hideButtons?: ToolbarButton[];
1649
+ disabled?: boolean;
1650
+ error?: boolean;
1651
+ helperText?: string;
1649
1652
  width?: number | string;
1650
1653
  height?: number | string;
1651
1654
  resizable?: boolean;
@@ -1662,6 +1665,42 @@ interface RufousTextContentProps {
1662
1665
  }
1663
1666
  declare const RufousTextContent: React__default.FC<RufousTextContentProps>;
1664
1667
 
1668
+ /**
1669
+ * Transforms legacy Jodit-style todo HTML into TipTap-compatible taskList HTML.
1670
+ *
1671
+ * Old Jodit format (two variants):
1672
+ *
1673
+ * Variant 1 (class-based):
1674
+ * <p class="todo-item">
1675
+ * <button class="todo-item-button" contenteditable="false">
1676
+ * <img src="...todo-blank.svg" alt="icon">
1677
+ * </button>
1678
+ * <span class="todo-item-text"> Todo text</span>
1679
+ * </p>
1680
+ *
1681
+ * Variant 2 (inline-styled):
1682
+ * <p class="todo-item" style="display: flex; ...">
1683
+ * <button class="todo-item-button" contenteditable="false" style="...">
1684
+ * <img src="...todo-blank.svg" alt="icon" style="...">
1685
+ * </button>
1686
+ * <span style="margin-top: -2px; flex-grow: 1;"> Todo text</span>
1687
+ * </p>
1688
+ *
1689
+ * New TipTap format:
1690
+ * <ul data-type="taskList">
1691
+ * <li data-status="todo">
1692
+ * <label><input type="checkbox"></label>
1693
+ * <div><p>Todo text</p></div>
1694
+ * </li>
1695
+ * </ul>
1696
+ */
1697
+ /**
1698
+ * Transforms legacy Jodit todo HTML to TipTap taskList format.
1699
+ * Non-todo content is passed through unchanged.
1700
+ * Consecutive todo items are grouped into a single <ul data-type="taskList">.
1701
+ */
1702
+ declare function transformLegacyTodos(html: string): string;
1703
+
1665
1704
  interface MentionItemData {
1666
1705
  id: string;
1667
1706
  name: string;
@@ -1669,4 +1708,4 @@ interface MentionItemData {
1669
1708
  shortName?: string;
1670
1709
  }
1671
1710
 
1672
- export { APP_THEMES, Accordion, AccordionDetails, type AccordionDetailsProps, type AccordionProps, AccordionSummary, type AccordionSummaryProps, type Action, ActivateUserIcon, AddButton, AddressLookup, ArchivedIcon, AssignGroupIcon, Autocomplete, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BaseDialog, Box, type BoxProps, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, CameraIcon, CancelButton, Card, CardActions, type CardActionsProps, CardContent, type CardContentProps, CardHeader, type CardHeaderProps, CardMedia, type CardMediaProps, type CardProps, Checkbox, type CheckboxProps, Chip, type ChipProps, CircularProgress, CircularProgressIcon, type CircularProgressIconProps, CloseIcon, Collapse, type CollapseProps, type Column, CopyIcon, DataGrid, type DataGridProps, DateField, type DateFieldProps, type DateFormatString, DateRangeField, type DateRangeFieldProps, type DateRangeValue, DifficultyAllIcon, DifficultyEasyIcon, DifficultyHardIcon, DifficultyMediumIcon, Divider, type DividerProps, DollarIcon, DownloadIcon, DownloadPdfIcon, Drawer, type DrawerProps, EditChatIcon, EditIcon, EngagementIcon, Fade, type FadeProps, FunctionIcon, Grid, type GridProps, Grow, type GrowProps, HelpOutlinedIcon, HierarchyIcon, IconButton, type IconButtonProps, ImageField, type ImageFieldProps, InactiveGroupIcon, IndustryIcon, InvoiceIcon, Link, type LinkProps, List, ListItem, ListItemButton, type ListItemButtonProps, ListItemIcon, type ListItemIconProps, type ListItemProps, ListItemText, type ListItemTextProps, type ListProps, ListSubheader, type ListSubheaderProps, LocationPinIcon, LogsIcon, Menu, MenuDivider, MenuItem, type MenuItemProps, MenuList, type MenuListProps, type MenuProps, MinExperienceIcon, NineDotMenuIcon, NotificationIcon, Paper, type PaperProps, PhoneField, type PhoneFieldProps, Popover, type PopoverProps, Popper, type PopperProps, ProjectIcon, QualificationsIcon, QuestionStatusAllIcon, QuestionStatusPrivateIcon, QuestionStatusPublicIcon, QuestionTypeAllIcon, QuestionTypeCodingIcon, QuestionTypeDescriptiveIcon, QuestionTypeMultipleIcon, QuestionTypeSingleIcon, Radio, RadioGroup, type RadioGroupProps, type RadioProps, Rating, type RatingProps, RefreshIcon, ResendInviteIcon, RolesIcon, RufousAiIcon, RufousBirdIcon, RufousLauncherIcon, RufousLogoLoader, type RufousLogoLoaderProps, RufousTextContent, type RufousTextContentProps, RufousTextEditor, type MentionItemData as RufousTextEditorMentionItem, type RufousTextEditorProps, RufousThemeProvider, Select, type SelectProps, SidebarIcon, Skeleton, type SkeletonProps, Slide, type SlideProps, Slider, type SliderProps, Snackbar, type SnackbarProps, SoftSkillsIcon, type SortDirection, Stack, type StackProps, StandardButton, Step, StepButton, type StepButtonProps, StepContent, type StepContentProps, StepLabel, type StepLabelProps, type StepProps, Stepper, type StepperProps, SubmitButton, SubscribeIcon, SuspendUserIcon, Switch, type SwitchProps, type SxProp, Tab, TabPanel, type TabPanelProps, type TabProps, Tabs, type TabsProps, TechnicalSkillsIcon, TextField, type TextFieldProps, TickIcon, TimerIcon, ToggleButton, ToggleButtonGroup, type ToggleButtonGroupProps, type ToggleButtonProps, Tooltip, type TooltipProps, TrashIcon, Typography, type TypographyProps, UnArchivedIcon, UnsubscribeIcon, UploadIcon, UserAssignIcon, ViewIcon, WorkItemIcon, Zoom, type ZoomProps, useRufousTheme };
1711
+ export { APP_THEMES, Accordion, AccordionDetails, type AccordionDetailsProps, type AccordionProps, AccordionSummary, type AccordionSummaryProps, type Action, ActivateUserIcon, AddButton, AddressLookup, ArchivedIcon, AssignGroupIcon, Autocomplete, type AutocompleteProps, Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, BaseDialog, Box, type BoxProps, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, CameraIcon, CancelButton, Card, CardActions, type CardActionsProps, CardContent, type CardContentProps, CardHeader, type CardHeaderProps, CardMedia, type CardMediaProps, type CardProps, Checkbox, type CheckboxProps, Chip, type ChipProps, CircularProgress, CircularProgressIcon, type CircularProgressIconProps, CloseIcon, Collapse, type CollapseProps, type Column, CopyIcon, DataGrid, type DataGridProps, DateField, type DateFieldProps, type DateFormatString, DateRangeField, type DateRangeFieldProps, type DateRangeValue, DifficultyAllIcon, DifficultyEasyIcon, DifficultyHardIcon, DifficultyMediumIcon, Divider, type DividerProps, DollarIcon, DownloadIcon, DownloadPdfIcon, Drawer, type DrawerProps, EditChatIcon, EditIcon, EngagementIcon, Fade, type FadeProps, FunctionIcon, Grid, type GridProps, Grow, type GrowProps, HelpOutlinedIcon, HierarchyIcon, IconButton, type IconButtonProps, ImageField, type ImageFieldProps, InactiveGroupIcon, IndustryIcon, InvoiceIcon, Link, type LinkProps, List, ListItem, ListItemButton, type ListItemButtonProps, ListItemIcon, type ListItemIconProps, type ListItemProps, ListItemText, type ListItemTextProps, type ListProps, ListSubheader, type ListSubheaderProps, LocationPinIcon, LogsIcon, Menu, MenuDivider, MenuItem, type MenuItemProps, MenuList, type MenuListProps, type MenuProps, MinExperienceIcon, NineDotMenuIcon, NotificationIcon, Paper, type PaperProps, PhoneField, type PhoneFieldProps, Popover, type PopoverProps, Popper, type PopperProps, ProjectIcon, QualificationsIcon, QuestionStatusAllIcon, QuestionStatusPrivateIcon, QuestionStatusPublicIcon, QuestionTypeAllIcon, QuestionTypeCodingIcon, QuestionTypeDescriptiveIcon, QuestionTypeMultipleIcon, QuestionTypeSingleIcon, Radio, RadioGroup, type RadioGroupProps, type RadioProps, Rating, type RatingProps, RefreshIcon, ResendInviteIcon, RolesIcon, RufousAiIcon, RufousBirdIcon, RufousLauncherIcon, RufousLogoLoader, type RufousLogoLoaderProps, RufousTextContent, type RufousTextContentProps, RufousTextEditor, type MentionItemData as RufousTextEditorMentionItem, type RufousTextEditorProps, RufousThemeProvider, Select, type SelectProps, SidebarIcon, Skeleton, type SkeletonProps, Slide, type SlideProps, Slider, type SliderProps, Snackbar, type SnackbarProps, SoftSkillsIcon, type SortDirection, Stack, type StackProps, StandardButton, Step, StepButton, type StepButtonProps, StepContent, type StepContentProps, StepLabel, type StepLabelProps, type StepProps, Stepper, type StepperProps, SubmitButton, SubscribeIcon, SuspendUserIcon, Switch, type SwitchProps, type SxProp, Tab, TabPanel, type TabPanelProps, type TabProps, Tabs, type TabsProps, TechnicalSkillsIcon, TextField, type TextFieldProps, TickIcon, TimerIcon, ToggleButton, ToggleButtonGroup, type ToggleButtonGroupProps, type ToggleButtonProps, Tooltip, type TooltipProps, TrashIcon, Typography, type TypographyProps, UnArchivedIcon, UnsubscribeIcon, UploadIcon, UserAssignIcon, ViewIcon, WorkItemIcon, Zoom, type ZoomProps, transformLegacyTodos, useRufousTheme };
package/dist/main.js CHANGED
@@ -9550,7 +9550,7 @@ var STATUS_COLORS = {
9550
9550
  };
9551
9551
  var CustomTaskItem = TaskItem.extend({
9552
9552
  addStorage() {
9553
- return { taskTodoEnabled: true };
9553
+ return { taskTodoEnabled: false };
9554
9554
  },
9555
9555
  addAttributes() {
9556
9556
  return {
@@ -9596,25 +9596,7 @@ var CustomTaskItem = TaskItem.extend({
9596
9596
  return false;
9597
9597
  }
9598
9598
  if (this.editor.storage.taskTodoEnabled === false) {
9599
- const afterContent2 = $from.parent.content.cut($from.parentOffset);
9600
- const schema2 = state.schema;
9601
- const tr2 = state.tr;
9602
- if (afterContent2.size > 0) {
9603
- tr2.delete($from.pos, $from.end());
9604
- }
9605
- let taskListDepth = -1;
9606
- for (let d = taskDepth - 1; d >= 0; d--) {
9607
- if ($from.node(d).type.name === "taskList") {
9608
- taskListDepth = d;
9609
- break;
9610
- }
9611
- }
9612
- const insertPos2 = taskListDepth !== -1 ? tr2.mapping.map($from.after(taskListDepth)) : tr2.mapping.map($from.after(taskDepth));
9613
- const newPara2 = afterContent2.size > 0 ? schema2.nodes.paragraph.create(null, afterContent2) : schema2.nodes.paragraph.create();
9614
- tr2.insert(insertPos2, newPara2);
9615
- tr2.setSelection(TextSelection.near(tr2.doc.resolve(insertPos2 + 1)));
9616
- this.editor.view.dispatch(tr2);
9617
- return true;
9599
+ return false;
9618
9600
  }
9619
9601
  const taskNode = $from.node(taskDepth);
9620
9602
  const status = taskNode.attrs.status || "todo";
@@ -9718,7 +9700,7 @@ var CustomTaskItem = TaskItem.extend({
9718
9700
  addNodeView() {
9719
9701
  return ({ node, HTMLAttributes, getPos, editor }) => {
9720
9702
  const li = document.createElement("li");
9721
- li.style.cssText = "display: flex; align-items: flex-start; gap: 8px; margin: 4px 0; list-style: none; ";
9703
+ li.style.cssText = "display: flex; align-items: flex-start; gap: 6px; margin: 0; list-style: none; line-height: 1.7; ";
9722
9704
  Object.entries(HTMLAttributes || {}).forEach(([key, val]) => {
9723
9705
  if (val != null && val !== false) {
9724
9706
  li.setAttribute(key, String(val));
@@ -9727,7 +9709,7 @@ var CustomTaskItem = TaskItem.extend({
9727
9709
  li.setAttribute("data-status", node.attrs.status || "todo");
9728
9710
  const label = document.createElement("label");
9729
9711
  label.contentEditable = "false";
9730
- label.style.cssText = "flex-shrink: 0; margin-top: 3px;";
9712
+ label.style.cssText = "flex-shrink: 0;";
9731
9713
  const checkbox = document.createElement("span");
9732
9714
  const updateCheckbox = (status) => {
9733
9715
  const colors = STATUS_COLORS[status] || STATUS_COLORS.todo;
@@ -9742,7 +9724,7 @@ var CustomTaskItem = TaskItem.extend({
9742
9724
  "border-radius: 3px",
9743
9725
  "cursor: pointer",
9744
9726
  "user-select: none",
9745
- "margin-top: 7px",
9727
+ "margin-top: 3px",
9746
9728
  "border: 2px solid " + colors.border,
9747
9729
  `background-image: url("${imageUrl}")`,
9748
9730
  "background-repeat: no-repeat",
@@ -9968,34 +9950,42 @@ var Dropdown = ({ trigger, children, className = "", keepOpen = false }) => {
9968
9950
  const menuRef = useRef26(null);
9969
9951
  useEffect23(() => {
9970
9952
  const handleClick = (e) => {
9971
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
9953
+ const target = e.target;
9954
+ if (ref.current && !ref.current.contains(target) && menuRef.current && !menuRef.current.contains(target)) {
9955
+ setOpen(false);
9956
+ }
9972
9957
  };
9973
9958
  document.addEventListener("mousedown", handleClick);
9974
9959
  return () => document.removeEventListener("mousedown", handleClick);
9975
9960
  }, []);
9976
9961
  useEffect23(() => {
9977
- if (!open || !menuRef.current) return;
9962
+ if (!open || !menuRef.current || !ref.current) return;
9978
9963
  const menu = menuRef.current;
9979
- menu.style.left = "0";
9980
- menu.style.right = "auto";
9981
- requestAnimationFrame(() => {
9982
- if (!menu) return;
9983
- const rect = menu.getBoundingClientRect();
9964
+ const trigger2 = ref.current;
9965
+ const position = () => {
9966
+ const triggerRect = trigger2.getBoundingClientRect();
9984
9967
  const vw = window.innerWidth;
9985
- const parentLeft = ref.current?.getBoundingClientRect().left || 0;
9986
- if (rect.right > vw - 8) {
9987
- menu.style.left = "auto";
9988
- menu.style.right = "0";
9989
- const newRect = menu.getBoundingClientRect();
9990
- if (newRect.left < 8) {
9991
- menu.style.left = `${8 - parentLeft}px`;
9992
- menu.style.right = "auto";
9968
+ const vh = window.innerHeight;
9969
+ let left = triggerRect.left;
9970
+ let top = triggerRect.bottom + 4;
9971
+ menu.style.left = `${left}px`;
9972
+ menu.style.top = `${top}px`;
9973
+ requestAnimationFrame(() => {
9974
+ if (!menu) return;
9975
+ const menuRect = menu.getBoundingClientRect();
9976
+ if (menuRect.right > vw - 8) {
9977
+ left = Math.max(8, triggerRect.right - menuRect.width);
9993
9978
  }
9994
- } else if (rect.left < 8) {
9995
- menu.style.left = `${8 - parentLeft}px`;
9996
- menu.style.right = "auto";
9997
- }
9998
- });
9979
+ if (left < 8) left = 8;
9980
+ if (menuRect.bottom > vh - 8) {
9981
+ top = triggerRect.top - menuRect.height - 4;
9982
+ if (top < 8) top = 8;
9983
+ }
9984
+ menu.style.left = `${left}px`;
9985
+ menu.style.top = `${top}px`;
9986
+ });
9987
+ };
9988
+ position();
9999
9989
  }, [open]);
10000
9990
  return /* @__PURE__ */ React112.createElement("div", { className: `dropdown ${className}`, ref }, /* @__PURE__ */ React112.createElement(
10001
9991
  "button",
@@ -10006,7 +9996,7 @@ var Dropdown = ({ trigger, children, className = "", keepOpen = false }) => {
10006
9996
  },
10007
9997
  trigger.label,
10008
9998
  /* @__PURE__ */ React112.createElement("span", { className: "dropdown-arrow" }, "\u25BE")
10009
- ), open && /* @__PURE__ */ React112.createElement("div", { ref: menuRef, className: "dropdown-menu", onClick: keepOpen ? void 0 : () => setOpen(false) }, typeof children === "function" ? children(() => setOpen(false)) : children));
9999
+ ), open && /* @__PURE__ */ React112.createElement("div", { ref: menuRef, className: "dropdown-menu dropdown-menu-fixed", onClick: keepOpen ? void 0 : () => setOpen(false) }, typeof children === "function" ? children(() => setOpen(false)) : children));
10010
10000
  };
10011
10001
  var InsertPanel = ({ editor, onClose, mode = "video" }) => {
10012
10002
  const [activeTab, setActiveTab] = useState30("link");
@@ -11424,6 +11414,79 @@ var VideoToolbar = ({ editor }) => {
11424
11414
  };
11425
11415
  var VideoToolbar_default = VideoToolbar;
11426
11416
 
11417
+ // lib/RufousTextEditor/legacyTodoTransform.ts
11418
+ var IMAGE_TO_STATUS = {
11419
+ "todo-blank.svg": "todo",
11420
+ "working.svg": "working",
11421
+ "blocked.svg": "blocked",
11422
+ "closed.svg": "resolved"
11423
+ };
11424
+ function getStatusFromImgSrc(src) {
11425
+ for (const [key, status] of Object.entries(IMAGE_TO_STATUS)) {
11426
+ if (src.includes(key)) return status;
11427
+ }
11428
+ return "todo";
11429
+ }
11430
+ function transformLegacyTodos(html) {
11431
+ if (!html || !html.includes("todo-item")) return html;
11432
+ const div = document.createElement("div");
11433
+ div.innerHTML = html;
11434
+ const children = Array.from(div.childNodes);
11435
+ const result = [];
11436
+ let currentTaskList = null;
11437
+ for (const child of children) {
11438
+ if (child instanceof HTMLElement && child.classList.contains("todo-item")) {
11439
+ const img = child.querySelector("button img, .todo-item-button img");
11440
+ const status = img ? getStatusFromImgSrc(img.src) : "todo";
11441
+ let text = "";
11442
+ const classSpan = child.querySelector(".todo-item-text");
11443
+ if (classSpan) {
11444
+ text = classSpan.textContent?.trim() || "";
11445
+ } else {
11446
+ const spans = child.querySelectorAll("span");
11447
+ for (const span of Array.from(spans)) {
11448
+ if (!span.closest("button") && !span.closest(".todo-item-button")) {
11449
+ text = span.textContent?.trim() || "";
11450
+ break;
11451
+ }
11452
+ }
11453
+ }
11454
+ const li = document.createElement("li");
11455
+ li.setAttribute("data-type", "taskItem");
11456
+ li.setAttribute("data-status", status);
11457
+ li.setAttribute("data-checked", "false");
11458
+ const label = document.createElement("label");
11459
+ const checkbox = document.createElement("input");
11460
+ checkbox.type = "checkbox";
11461
+ const labelSpan = document.createElement("span");
11462
+ label.appendChild(checkbox);
11463
+ label.appendChild(labelSpan);
11464
+ const contentDiv = document.createElement("div");
11465
+ const p = document.createElement("p");
11466
+ if (text) {
11467
+ p.textContent = text;
11468
+ }
11469
+ contentDiv.appendChild(p);
11470
+ li.appendChild(label);
11471
+ li.appendChild(contentDiv);
11472
+ if (!currentTaskList) {
11473
+ currentTaskList = document.createElement("ul");
11474
+ currentTaskList.setAttribute("data-type", "taskList");
11475
+ result.push(currentTaskList);
11476
+ }
11477
+ currentTaskList.appendChild(li);
11478
+ } else {
11479
+ currentTaskList = null;
11480
+ result.push(child);
11481
+ }
11482
+ }
11483
+ const output = document.createElement("div");
11484
+ for (const node of result) {
11485
+ output.appendChild(node);
11486
+ }
11487
+ return output.innerHTML;
11488
+ }
11489
+
11427
11490
  // lib/RufousTextEditor/RufousTextEditor.tsx
11428
11491
  var VARIANT_BUTTONS = {
11429
11492
  default: [
@@ -11499,6 +11562,9 @@ var RufousTextEditor = ({
11499
11562
  variant = "default",
11500
11563
  buttons,
11501
11564
  hideButtons,
11565
+ disabled = false,
11566
+ error = false,
11567
+ helperText,
11502
11568
  width,
11503
11569
  height,
11504
11570
  resizable = false,
@@ -11524,8 +11590,9 @@ var RufousTextEditor = ({
11524
11590
  useEffect26(() => {
11525
11591
  onBlurRef.current = onBlur;
11526
11592
  }, [onBlur]);
11593
+ const isEditable = editable && !disabled;
11527
11594
  const editor = useEditor({
11528
- editable,
11595
+ editable: isEditable,
11529
11596
  extensions: [
11530
11597
  StarterKit,
11531
11598
  Placeholder.configure({
@@ -11614,19 +11681,28 @@ var RufousTextEditor = ({
11614
11681
  return false;
11615
11682
  }
11616
11683
  },
11617
- content: initialContent || "",
11684
+ content: transformLegacyTodos(initialContent || ""),
11618
11685
  onUpdate: ({ editor: e }) => {
11619
11686
  onChangeRef.current?.(e.getHTML(), e.getJSON());
11620
11687
  }
11621
11688
  });
11689
+ const wrapperRef = useRef29(null);
11622
11690
  useEffect26(() => {
11623
11691
  if (!editor) return;
11624
- const handler = () => {
11625
- onBlurRef.current?.(editor.getHTML(), editor.getJSON());
11692
+ let blurTimer = null;
11693
+ const handler = ({ event }) => {
11694
+ const relatedTarget = event?.relatedTarget;
11695
+ if (relatedTarget && wrapperRef.current?.contains(relatedTarget)) return;
11696
+ if (relatedTarget?.closest?.(".rf-rte-mention-dropdown, .tippy-box, .link-modal-overlay, .ai-modal-overlay, .modal-overlay, .rf-spell-tooltip")) return;
11697
+ if (blurTimer) clearTimeout(blurTimer);
11698
+ blurTimer = setTimeout(() => {
11699
+ onBlurRef.current?.(editor.getHTML(), editor.getJSON());
11700
+ }, 100);
11626
11701
  };
11627
11702
  editor.on("blur", handler);
11628
11703
  return () => {
11629
11704
  editor.off("blur", handler);
11705
+ if (blurTimer) clearTimeout(blurTimer);
11630
11706
  };
11631
11707
  }, [editor]);
11632
11708
  const setLinkRef = useRef29(null);
@@ -11777,7 +11853,8 @@ var RufousTextEditor = ({
11777
11853
  return /* @__PURE__ */ React115.createElement(
11778
11854
  "div",
11779
11855
  {
11780
- className: `rf-rte-wrapper editor-wrapper ${resizable ? "rf-rte-resizable" : ""} ${sxClass} ${className || ""}`,
11856
+ ref: wrapperRef,
11857
+ className: `rf-rte-wrapper editor-wrapper ${resizable ? "rf-rte-resizable" : ""} ${disabled ? "rf-rte-disabled" : ""} ${error ? "rf-rte-error" : ""} ${sxClass} ${className || ""}`,
11781
11858
  style: {
11782
11859
  ...style,
11783
11860
  ...width ? { width: typeof width === "number" ? `${width}px` : width } : {},
@@ -11952,17 +12029,19 @@ var RufousTextEditor = ({
11952
12029
  }
11953
12030
  ), "No follow"))), /* @__PURE__ */ React115.createElement("div", { className: "link-modal-footer" }, /* @__PURE__ */ React115.createElement("button", { className: "link-modal-btn-unlink", onClick: handleLinkRemove }, "Unlink"), /* @__PURE__ */ React115.createElement("button", { className: "link-modal-btn-apply", onClick: handleLinkSubmit }, "Update")))),
11954
12031
  document.body
11955
- ))
12032
+ )),
12033
+ helperText && /* @__PURE__ */ React115.createElement("div", { className: `rf-rte-helper-text ${error ? "rf-rte-helper-error" : ""}` }, helperText)
11956
12034
  );
11957
12035
  };
11958
12036
  var RufousTextContent = ({ content, className, style, sx }) => {
11959
12037
  const sxClass = useSx(sx);
12038
+ const transformedContent = useMemo4(() => transformLegacyTodos(content || ""), [content]);
11960
12039
  return /* @__PURE__ */ React115.createElement(
11961
12040
  "div",
11962
12041
  {
11963
12042
  className: `rf-rte-content ${sxClass} ${className || ""}`,
11964
12043
  style,
11965
- dangerouslySetInnerHTML: { __html: content }
12044
+ dangerouslySetInnerHTML: { __html: transformedContent }
11966
12045
  }
11967
12046
  );
11968
12047
  };
@@ -12103,5 +12182,6 @@ export {
12103
12182
  viewIcon_default as ViewIcon,
12104
12183
  workItemIcon_default as WorkItemIcon,
12105
12184
  Zoom,
12185
+ transformLegacyTodos,
12106
12186
  useRufousTheme
12107
12187
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rufous/ui",
3
3
  "private": false,
4
- "version": "0.2.59",
4
+ "version": "0.2.61",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/main.css",