@rufous/ui 0.2.59 → 0.2.60

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 {
@@ -9660,27 +9661,6 @@ var CustomTaskItem = import_extension_task_item.default.extend({
9660
9661
  }
9661
9662
  return false;
9662
9663
  }
9663
- 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;
9683
- }
9684
9664
  const taskNode = $from.node(taskDepth);
9685
9665
  const status = taskNode.attrs.status || "todo";
9686
9666
  const schema = state.schema;
@@ -9783,7 +9763,7 @@ var CustomTaskItem = import_extension_task_item.default.extend({
9783
9763
  addNodeView() {
9784
9764
  return ({ node, HTMLAttributes, getPos, editor }) => {
9785
9765
  const li = document.createElement("li");
9786
- li.style.cssText = "display: flex; align-items: flex-start; gap: 8px; margin: 4px 0; list-style: none; ";
9766
+ li.style.cssText = "display: flex; align-items: flex-start; gap: 6px; margin: 0; list-style: none; ";
9787
9767
  Object.entries(HTMLAttributes || {}).forEach(([key, val]) => {
9788
9768
  if (val != null && val !== false) {
9789
9769
  li.setAttribute(key, String(val));
@@ -9792,7 +9772,7 @@ var CustomTaskItem = import_extension_task_item.default.extend({
9792
9772
  li.setAttribute("data-status", node.attrs.status || "todo");
9793
9773
  const label = document.createElement("label");
9794
9774
  label.contentEditable = "false";
9795
- label.style.cssText = "flex-shrink: 0; margin-top: 3px;";
9775
+ label.style.cssText = "flex-shrink: 0;";
9796
9776
  const checkbox = document.createElement("span");
9797
9777
  const updateCheckbox = (status) => {
9798
9778
  const colors = STATUS_COLORS[status] || STATUS_COLORS.todo;
@@ -9807,7 +9787,7 @@ var CustomTaskItem = import_extension_task_item.default.extend({
9807
9787
  "border-radius: 3px",
9808
9788
  "cursor: pointer",
9809
9789
  "user-select: none",
9810
- "margin-top: 7px",
9790
+ "margin-top: 3px",
9811
9791
  "border: 2px solid " + colors.border,
9812
9792
  `background-image: url("${imageUrl}")`,
9813
9793
  "background-repeat: no-repeat",
@@ -10033,34 +10013,42 @@ var Dropdown = ({ trigger, children, className = "", keepOpen = false }) => {
10033
10013
  const menuRef = (0, import_react55.useRef)(null);
10034
10014
  (0, import_react55.useEffect)(() => {
10035
10015
  const handleClick = (e) => {
10036
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
10016
+ const target = e.target;
10017
+ if (ref.current && !ref.current.contains(target) && menuRef.current && !menuRef.current.contains(target)) {
10018
+ setOpen(false);
10019
+ }
10037
10020
  };
10038
10021
  document.addEventListener("mousedown", handleClick);
10039
10022
  return () => document.removeEventListener("mousedown", handleClick);
10040
10023
  }, []);
10041
10024
  (0, import_react55.useEffect)(() => {
10042
- if (!open || !menuRef.current) return;
10025
+ if (!open || !menuRef.current || !ref.current) return;
10043
10026
  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();
10027
+ const trigger2 = ref.current;
10028
+ const position = () => {
10029
+ const triggerRect = trigger2.getBoundingClientRect();
10049
10030
  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";
10031
+ const vh = window.innerHeight;
10032
+ let left = triggerRect.left;
10033
+ let top = triggerRect.bottom + 4;
10034
+ menu.style.left = `${left}px`;
10035
+ menu.style.top = `${top}px`;
10036
+ requestAnimationFrame(() => {
10037
+ if (!menu) return;
10038
+ const menuRect = menu.getBoundingClientRect();
10039
+ if (menuRect.right > vw - 8) {
10040
+ left = Math.max(8, triggerRect.right - menuRect.width);
10058
10041
  }
10059
- } else if (rect.left < 8) {
10060
- menu.style.left = `${8 - parentLeft}px`;
10061
- menu.style.right = "auto";
10062
- }
10063
- });
10042
+ if (left < 8) left = 8;
10043
+ if (menuRect.bottom > vh - 8) {
10044
+ top = triggerRect.top - menuRect.height - 4;
10045
+ if (top < 8) top = 8;
10046
+ }
10047
+ menu.style.left = `${left}px`;
10048
+ menu.style.top = `${top}px`;
10049
+ });
10050
+ };
10051
+ position();
10064
10052
  }, [open]);
10065
10053
  return /* @__PURE__ */ import_react55.default.createElement("div", { className: `dropdown ${className}`, ref }, /* @__PURE__ */ import_react55.default.createElement(
10066
10054
  "button",
@@ -10071,7 +10059,7 @@ var Dropdown = ({ trigger, children, className = "", keepOpen = false }) => {
10071
10059
  },
10072
10060
  trigger.label,
10073
10061
  /* @__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));
10062
+ ), 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
10063
  };
10076
10064
  var InsertPanel = ({ editor, onClose, mode = "video" }) => {
10077
10065
  const [activeTab, setActiveTab] = (0, import_react55.useState)("link");
@@ -11489,6 +11477,79 @@ var VideoToolbar = ({ editor }) => {
11489
11477
  };
11490
11478
  var VideoToolbar_default = VideoToolbar;
11491
11479
 
11480
+ // lib/RufousTextEditor/legacyTodoTransform.ts
11481
+ var IMAGE_TO_STATUS = {
11482
+ "todo-blank.svg": "todo",
11483
+ "working.svg": "working",
11484
+ "blocked.svg": "blocked",
11485
+ "closed.svg": "resolved"
11486
+ };
11487
+ function getStatusFromImgSrc(src) {
11488
+ for (const [key, status] of Object.entries(IMAGE_TO_STATUS)) {
11489
+ if (src.includes(key)) return status;
11490
+ }
11491
+ return "todo";
11492
+ }
11493
+ function transformLegacyTodos(html) {
11494
+ if (!html || !html.includes("todo-item")) return html;
11495
+ const div = document.createElement("div");
11496
+ div.innerHTML = html;
11497
+ const children = Array.from(div.childNodes);
11498
+ const result = [];
11499
+ let currentTaskList = null;
11500
+ for (const child of children) {
11501
+ if (child instanceof HTMLElement && child.classList.contains("todo-item")) {
11502
+ const img = child.querySelector("button img, .todo-item-button img");
11503
+ const status = img ? getStatusFromImgSrc(img.src) : "todo";
11504
+ let text = "";
11505
+ const classSpan = child.querySelector(".todo-item-text");
11506
+ if (classSpan) {
11507
+ text = classSpan.textContent?.trim() || "";
11508
+ } else {
11509
+ const spans = child.querySelectorAll("span");
11510
+ for (const span of Array.from(spans)) {
11511
+ if (!span.closest("button") && !span.closest(".todo-item-button")) {
11512
+ text = span.textContent?.trim() || "";
11513
+ break;
11514
+ }
11515
+ }
11516
+ }
11517
+ const li = document.createElement("li");
11518
+ li.setAttribute("data-type", "taskItem");
11519
+ li.setAttribute("data-status", status);
11520
+ li.setAttribute("data-checked", "false");
11521
+ const label = document.createElement("label");
11522
+ const checkbox = document.createElement("input");
11523
+ checkbox.type = "checkbox";
11524
+ const labelSpan = document.createElement("span");
11525
+ label.appendChild(checkbox);
11526
+ label.appendChild(labelSpan);
11527
+ const contentDiv = document.createElement("div");
11528
+ const p = document.createElement("p");
11529
+ if (text) {
11530
+ p.textContent = text;
11531
+ }
11532
+ contentDiv.appendChild(p);
11533
+ li.appendChild(label);
11534
+ li.appendChild(contentDiv);
11535
+ if (!currentTaskList) {
11536
+ currentTaskList = document.createElement("ul");
11537
+ currentTaskList.setAttribute("data-type", "taskList");
11538
+ result.push(currentTaskList);
11539
+ }
11540
+ currentTaskList.appendChild(li);
11541
+ } else {
11542
+ currentTaskList = null;
11543
+ result.push(child);
11544
+ }
11545
+ }
11546
+ const output = document.createElement("div");
11547
+ for (const node of result) {
11548
+ output.appendChild(node);
11549
+ }
11550
+ return output.innerHTML;
11551
+ }
11552
+
11492
11553
  // lib/RufousTextEditor/RufousTextEditor.tsx
11493
11554
  var VARIANT_BUTTONS = {
11494
11555
  default: [
@@ -11564,6 +11625,9 @@ var RufousTextEditor = ({
11564
11625
  variant = "default",
11565
11626
  buttons,
11566
11627
  hideButtons,
11628
+ disabled = false,
11629
+ error = false,
11630
+ helperText,
11567
11631
  width,
11568
11632
  height,
11569
11633
  resizable = false,
@@ -11589,8 +11653,9 @@ var RufousTextEditor = ({
11589
11653
  (0, import_react58.useEffect)(() => {
11590
11654
  onBlurRef.current = onBlur;
11591
11655
  }, [onBlur]);
11656
+ const isEditable = editable && !disabled;
11592
11657
  const editor = (0, import_react59.useEditor)({
11593
- editable,
11658
+ editable: isEditable,
11594
11659
  extensions: [
11595
11660
  import_starter_kit.default,
11596
11661
  import_extension_placeholder.default.configure({
@@ -11679,14 +11744,17 @@ var RufousTextEditor = ({
11679
11744
  return false;
11680
11745
  }
11681
11746
  },
11682
- content: initialContent || "",
11747
+ content: transformLegacyTodos(initialContent || ""),
11683
11748
  onUpdate: ({ editor: e }) => {
11684
11749
  onChangeRef.current?.(e.getHTML(), e.getJSON());
11685
11750
  }
11686
11751
  });
11752
+ const wrapperRef = (0, import_react58.useRef)(null);
11687
11753
  (0, import_react58.useEffect)(() => {
11688
11754
  if (!editor) return;
11689
- const handler = () => {
11755
+ const handler = ({ event }) => {
11756
+ const relatedTarget = event?.relatedTarget;
11757
+ if (relatedTarget && wrapperRef.current?.contains(relatedTarget)) return;
11690
11758
  onBlurRef.current?.(editor.getHTML(), editor.getJSON());
11691
11759
  };
11692
11760
  editor.on("blur", handler);
@@ -11842,7 +11910,8 @@ var RufousTextEditor = ({
11842
11910
  return /* @__PURE__ */ import_react58.default.createElement(
11843
11911
  "div",
11844
11912
  {
11845
- className: `rf-rte-wrapper editor-wrapper ${resizable ? "rf-rte-resizable" : ""} ${sxClass} ${className || ""}`,
11913
+ ref: wrapperRef,
11914
+ className: `rf-rte-wrapper editor-wrapper ${resizable ? "rf-rte-resizable" : ""} ${disabled ? "rf-rte-disabled" : ""} ${error ? "rf-rte-error" : ""} ${sxClass} ${className || ""}`,
11846
11915
  style: {
11847
11916
  ...style,
11848
11917
  ...width ? { width: typeof width === "number" ? `${width}px` : width } : {},
@@ -12017,17 +12086,19 @@ var RufousTextEditor = ({
12017
12086
  }
12018
12087
  ), "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
12088
  document.body
12020
- ))
12089
+ )),
12090
+ helperText && /* @__PURE__ */ import_react58.default.createElement("div", { className: `rf-rte-helper-text ${error ? "rf-rte-helper-error" : ""}` }, helperText)
12021
12091
  );
12022
12092
  };
12023
12093
  var RufousTextContent = ({ content, className, style, sx }) => {
12024
12094
  const sxClass = useSx(sx);
12095
+ const transformedContent = (0, import_react58.useMemo)(() => transformLegacyTodos(content || ""), [content]);
12025
12096
  return /* @__PURE__ */ import_react58.default.createElement(
12026
12097
  "div",
12027
12098
  {
12028
12099
  className: `rf-rte-content ${sxClass} ${className || ""}`,
12029
12100
  style,
12030
- dangerouslySetInnerHTML: { __html: content }
12101
+ dangerouslySetInnerHTML: { __html: transformedContent }
12031
12102
  }
12032
12103
  );
12033
12104
  };
@@ -12169,5 +12240,6 @@ var RufousTextContent = ({ content, className, style, sx }) => {
12169
12240
  ViewIcon,
12170
12241
  WorkItemIcon,
12171
12242
  Zoom,
12243
+ transformLegacyTodos,
12172
12244
  useRufousTheme
12173
12245
  });
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,7 +7284,10 @@ 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;
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 {
@@ -9595,27 +9595,6 @@ var CustomTaskItem = TaskItem.extend({
9595
9595
  }
9596
9596
  return false;
9597
9597
  }
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;
9618
- }
9619
9598
  const taskNode = $from.node(taskDepth);
9620
9599
  const status = taskNode.attrs.status || "todo";
9621
9600
  const schema = state.schema;
@@ -9718,7 +9697,7 @@ var CustomTaskItem = TaskItem.extend({
9718
9697
  addNodeView() {
9719
9698
  return ({ node, HTMLAttributes, getPos, editor }) => {
9720
9699
  const li = document.createElement("li");
9721
- li.style.cssText = "display: flex; align-items: flex-start; gap: 8px; margin: 4px 0; list-style: none; ";
9700
+ li.style.cssText = "display: flex; align-items: flex-start; gap: 6px; margin: 0; list-style: none; ";
9722
9701
  Object.entries(HTMLAttributes || {}).forEach(([key, val]) => {
9723
9702
  if (val != null && val !== false) {
9724
9703
  li.setAttribute(key, String(val));
@@ -9727,7 +9706,7 @@ var CustomTaskItem = TaskItem.extend({
9727
9706
  li.setAttribute("data-status", node.attrs.status || "todo");
9728
9707
  const label = document.createElement("label");
9729
9708
  label.contentEditable = "false";
9730
- label.style.cssText = "flex-shrink: 0; margin-top: 3px;";
9709
+ label.style.cssText = "flex-shrink: 0;";
9731
9710
  const checkbox = document.createElement("span");
9732
9711
  const updateCheckbox = (status) => {
9733
9712
  const colors = STATUS_COLORS[status] || STATUS_COLORS.todo;
@@ -9742,7 +9721,7 @@ var CustomTaskItem = TaskItem.extend({
9742
9721
  "border-radius: 3px",
9743
9722
  "cursor: pointer",
9744
9723
  "user-select: none",
9745
- "margin-top: 7px",
9724
+ "margin-top: 3px",
9746
9725
  "border: 2px solid " + colors.border,
9747
9726
  `background-image: url("${imageUrl}")`,
9748
9727
  "background-repeat: no-repeat",
@@ -9968,34 +9947,42 @@ var Dropdown = ({ trigger, children, className = "", keepOpen = false }) => {
9968
9947
  const menuRef = useRef26(null);
9969
9948
  useEffect23(() => {
9970
9949
  const handleClick = (e) => {
9971
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
9950
+ const target = e.target;
9951
+ if (ref.current && !ref.current.contains(target) && menuRef.current && !menuRef.current.contains(target)) {
9952
+ setOpen(false);
9953
+ }
9972
9954
  };
9973
9955
  document.addEventListener("mousedown", handleClick);
9974
9956
  return () => document.removeEventListener("mousedown", handleClick);
9975
9957
  }, []);
9976
9958
  useEffect23(() => {
9977
- if (!open || !menuRef.current) return;
9959
+ if (!open || !menuRef.current || !ref.current) return;
9978
9960
  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();
9961
+ const trigger2 = ref.current;
9962
+ const position = () => {
9963
+ const triggerRect = trigger2.getBoundingClientRect();
9984
9964
  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";
9965
+ const vh = window.innerHeight;
9966
+ let left = triggerRect.left;
9967
+ let top = triggerRect.bottom + 4;
9968
+ menu.style.left = `${left}px`;
9969
+ menu.style.top = `${top}px`;
9970
+ requestAnimationFrame(() => {
9971
+ if (!menu) return;
9972
+ const menuRect = menu.getBoundingClientRect();
9973
+ if (menuRect.right > vw - 8) {
9974
+ left = Math.max(8, triggerRect.right - menuRect.width);
9993
9975
  }
9994
- } else if (rect.left < 8) {
9995
- menu.style.left = `${8 - parentLeft}px`;
9996
- menu.style.right = "auto";
9997
- }
9998
- });
9976
+ if (left < 8) left = 8;
9977
+ if (menuRect.bottom > vh - 8) {
9978
+ top = triggerRect.top - menuRect.height - 4;
9979
+ if (top < 8) top = 8;
9980
+ }
9981
+ menu.style.left = `${left}px`;
9982
+ menu.style.top = `${top}px`;
9983
+ });
9984
+ };
9985
+ position();
9999
9986
  }, [open]);
10000
9987
  return /* @__PURE__ */ React112.createElement("div", { className: `dropdown ${className}`, ref }, /* @__PURE__ */ React112.createElement(
10001
9988
  "button",
@@ -10006,7 +9993,7 @@ var Dropdown = ({ trigger, children, className = "", keepOpen = false }) => {
10006
9993
  },
10007
9994
  trigger.label,
10008
9995
  /* @__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));
9996
+ ), 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
9997
  };
10011
9998
  var InsertPanel = ({ editor, onClose, mode = "video" }) => {
10012
9999
  const [activeTab, setActiveTab] = useState30("link");
@@ -11424,6 +11411,79 @@ var VideoToolbar = ({ editor }) => {
11424
11411
  };
11425
11412
  var VideoToolbar_default = VideoToolbar;
11426
11413
 
11414
+ // lib/RufousTextEditor/legacyTodoTransform.ts
11415
+ var IMAGE_TO_STATUS = {
11416
+ "todo-blank.svg": "todo",
11417
+ "working.svg": "working",
11418
+ "blocked.svg": "blocked",
11419
+ "closed.svg": "resolved"
11420
+ };
11421
+ function getStatusFromImgSrc(src) {
11422
+ for (const [key, status] of Object.entries(IMAGE_TO_STATUS)) {
11423
+ if (src.includes(key)) return status;
11424
+ }
11425
+ return "todo";
11426
+ }
11427
+ function transformLegacyTodos(html) {
11428
+ if (!html || !html.includes("todo-item")) return html;
11429
+ const div = document.createElement("div");
11430
+ div.innerHTML = html;
11431
+ const children = Array.from(div.childNodes);
11432
+ const result = [];
11433
+ let currentTaskList = null;
11434
+ for (const child of children) {
11435
+ if (child instanceof HTMLElement && child.classList.contains("todo-item")) {
11436
+ const img = child.querySelector("button img, .todo-item-button img");
11437
+ const status = img ? getStatusFromImgSrc(img.src) : "todo";
11438
+ let text = "";
11439
+ const classSpan = child.querySelector(".todo-item-text");
11440
+ if (classSpan) {
11441
+ text = classSpan.textContent?.trim() || "";
11442
+ } else {
11443
+ const spans = child.querySelectorAll("span");
11444
+ for (const span of Array.from(spans)) {
11445
+ if (!span.closest("button") && !span.closest(".todo-item-button")) {
11446
+ text = span.textContent?.trim() || "";
11447
+ break;
11448
+ }
11449
+ }
11450
+ }
11451
+ const li = document.createElement("li");
11452
+ li.setAttribute("data-type", "taskItem");
11453
+ li.setAttribute("data-status", status);
11454
+ li.setAttribute("data-checked", "false");
11455
+ const label = document.createElement("label");
11456
+ const checkbox = document.createElement("input");
11457
+ checkbox.type = "checkbox";
11458
+ const labelSpan = document.createElement("span");
11459
+ label.appendChild(checkbox);
11460
+ label.appendChild(labelSpan);
11461
+ const contentDiv = document.createElement("div");
11462
+ const p = document.createElement("p");
11463
+ if (text) {
11464
+ p.textContent = text;
11465
+ }
11466
+ contentDiv.appendChild(p);
11467
+ li.appendChild(label);
11468
+ li.appendChild(contentDiv);
11469
+ if (!currentTaskList) {
11470
+ currentTaskList = document.createElement("ul");
11471
+ currentTaskList.setAttribute("data-type", "taskList");
11472
+ result.push(currentTaskList);
11473
+ }
11474
+ currentTaskList.appendChild(li);
11475
+ } else {
11476
+ currentTaskList = null;
11477
+ result.push(child);
11478
+ }
11479
+ }
11480
+ const output = document.createElement("div");
11481
+ for (const node of result) {
11482
+ output.appendChild(node);
11483
+ }
11484
+ return output.innerHTML;
11485
+ }
11486
+
11427
11487
  // lib/RufousTextEditor/RufousTextEditor.tsx
11428
11488
  var VARIANT_BUTTONS = {
11429
11489
  default: [
@@ -11499,6 +11559,9 @@ var RufousTextEditor = ({
11499
11559
  variant = "default",
11500
11560
  buttons,
11501
11561
  hideButtons,
11562
+ disabled = false,
11563
+ error = false,
11564
+ helperText,
11502
11565
  width,
11503
11566
  height,
11504
11567
  resizable = false,
@@ -11524,8 +11587,9 @@ var RufousTextEditor = ({
11524
11587
  useEffect26(() => {
11525
11588
  onBlurRef.current = onBlur;
11526
11589
  }, [onBlur]);
11590
+ const isEditable = editable && !disabled;
11527
11591
  const editor = useEditor({
11528
- editable,
11592
+ editable: isEditable,
11529
11593
  extensions: [
11530
11594
  StarterKit,
11531
11595
  Placeholder.configure({
@@ -11614,14 +11678,17 @@ var RufousTextEditor = ({
11614
11678
  return false;
11615
11679
  }
11616
11680
  },
11617
- content: initialContent || "",
11681
+ content: transformLegacyTodos(initialContent || ""),
11618
11682
  onUpdate: ({ editor: e }) => {
11619
11683
  onChangeRef.current?.(e.getHTML(), e.getJSON());
11620
11684
  }
11621
11685
  });
11686
+ const wrapperRef = useRef29(null);
11622
11687
  useEffect26(() => {
11623
11688
  if (!editor) return;
11624
- const handler = () => {
11689
+ const handler = ({ event }) => {
11690
+ const relatedTarget = event?.relatedTarget;
11691
+ if (relatedTarget && wrapperRef.current?.contains(relatedTarget)) return;
11625
11692
  onBlurRef.current?.(editor.getHTML(), editor.getJSON());
11626
11693
  };
11627
11694
  editor.on("blur", handler);
@@ -11777,7 +11844,8 @@ var RufousTextEditor = ({
11777
11844
  return /* @__PURE__ */ React115.createElement(
11778
11845
  "div",
11779
11846
  {
11780
- className: `rf-rte-wrapper editor-wrapper ${resizable ? "rf-rte-resizable" : ""} ${sxClass} ${className || ""}`,
11847
+ ref: wrapperRef,
11848
+ className: `rf-rte-wrapper editor-wrapper ${resizable ? "rf-rte-resizable" : ""} ${disabled ? "rf-rte-disabled" : ""} ${error ? "rf-rte-error" : ""} ${sxClass} ${className || ""}`,
11781
11849
  style: {
11782
11850
  ...style,
11783
11851
  ...width ? { width: typeof width === "number" ? `${width}px` : width } : {},
@@ -11952,17 +12020,19 @@ var RufousTextEditor = ({
11952
12020
  }
11953
12021
  ), "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
12022
  document.body
11955
- ))
12023
+ )),
12024
+ helperText && /* @__PURE__ */ React115.createElement("div", { className: `rf-rte-helper-text ${error ? "rf-rte-helper-error" : ""}` }, helperText)
11956
12025
  );
11957
12026
  };
11958
12027
  var RufousTextContent = ({ content, className, style, sx }) => {
11959
12028
  const sxClass = useSx(sx);
12029
+ const transformedContent = useMemo4(() => transformLegacyTodos(content || ""), [content]);
11960
12030
  return /* @__PURE__ */ React115.createElement(
11961
12031
  "div",
11962
12032
  {
11963
12033
  className: `rf-rte-content ${sxClass} ${className || ""}`,
11964
12034
  style,
11965
- dangerouslySetInnerHTML: { __html: content }
12035
+ dangerouslySetInnerHTML: { __html: transformedContent }
11966
12036
  }
11967
12037
  );
11968
12038
  };
@@ -12103,5 +12173,6 @@ export {
12103
12173
  viewIcon_default as ViewIcon,
12104
12174
  workItemIcon_default as WorkItemIcon,
12105
12175
  Zoom,
12176
+ transformLegacyTodos,
12106
12177
  useRufousTheme
12107
12178
  };
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.60",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/main.css",