@trafica/editor 1.0.31 → 1.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { forwardRef, useRef, useEffect, useCallback, useMemo, useImperativeHandle, useState, useLayoutEffect, useSyncExternalStore } from 'react';
2
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
1
+ import { useSyncExternalStore, useRef, useCallback, useState, useEffect, useLayoutEffect, useMemo } from 'react';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { createPortal } from 'react-dom';
4
4
 
5
5
  // src/editor/core/DocumentModel.ts
@@ -659,9 +659,16 @@ function applyTransaction(state, tr) {
659
659
  case "split_block":
660
660
  doc = splitBlock(doc, step.path, step.offset);
661
661
  break;
662
- case "join_blocks":
662
+ case "join_blocks": {
663
+ const joinIdx = step.path[step.path.length - 1];
664
+ const prevPath = [...step.path.slice(0, -1), joinIdx - 1];
665
+ const prevNode = getNodeAtPath(doc, prevPath);
666
+ const prevTextLen = prevNode && "children" in prevNode ? collectText(prevNode).length : 0;
663
667
  doc = joinBlocks(doc, step.path);
668
+ const joinPos = getPosFromCharOffset(doc, prevPath, prevTextLen);
669
+ selection = { anchor: joinPos, focus: joinPos, isCollapsed: true };
664
670
  break;
671
+ }
665
672
  case "set_node_type":
666
673
  doc = setBlockType(doc, step.path, step.nodeType, step.attrs);
667
674
  break;
@@ -7824,7 +7831,7 @@ var jsonSerializer = {
7824
7831
  }
7825
7832
  }
7826
7833
  };
7827
- function EditorCore({
7834
+ function Editor({
7828
7835
  engine,
7829
7836
  placeholder = "Start writing...",
7830
7837
  className = "",
@@ -7832,14 +7839,11 @@ function EditorCore({
7832
7839
  onHTMLChange,
7833
7840
  onJSONChange,
7834
7841
  onOpenLinkPopup,
7835
- onUploadImage: _onUploadImage,
7836
- onFocus: onFocusProp,
7837
- onBlur: onBlurProp,
7838
- hideToolbar = false,
7839
- toolbarConfig
7842
+ onUploadImage: _onUploadImage
7840
7843
  }) {
7841
7844
  const containerRef = useRef(null);
7842
7845
  const isRenderingRef = useRef(false);
7846
+ const skipRestoreSelectionRef = useRef(false);
7843
7847
  const scrollCaretIntoView = useCallback(() => {
7844
7848
  var _a, _b;
7845
7849
  const sel = window.getSelection();
@@ -7952,8 +7956,14 @@ function EditorCore({
7952
7956
  useLayoutEffect(() => {
7953
7957
  const container = containerRef.current;
7954
7958
  if (!container || !state.selection) return;
7959
+ if (skipRestoreSelectionRef.current) {
7960
+ skipRestoreSelectionRef.current = false;
7961
+ return;
7962
+ }
7963
+ isRenderingRef.current = true;
7955
7964
  restoreSelection(container, state.selection);
7956
7965
  scrollCaretIntoView();
7966
+ isRenderingRef.current = false;
7957
7967
  }, [state.selection]);
7958
7968
  useEffect(() => {
7959
7969
  const container = containerRef.current;
@@ -8018,6 +8028,7 @@ function EditorCore({
8018
8028
  if (currentSelection && JSON.stringify(currentSelection.anchor) === JSON.stringify(captured.anchor) && JSON.stringify(currentSelection.focus) === JSON.stringify(captured.focus)) {
8019
8029
  return;
8020
8030
  }
8031
+ skipRestoreSelectionRef.current = true;
8021
8032
  const tr = createTransaction();
8022
8033
  tr.steps.push(tr_setSelection(captured));
8023
8034
  engine.dispatch(tr);
@@ -8044,11 +8055,7 @@ function EditorCore({
8044
8055
  );
8045
8056
  engine.dispatch(tr);
8046
8057
  }
8047
- onFocusProp == null ? void 0 : onFocusProp();
8048
- }, [engine, onFocusProp]);
8049
- const handleBlur = useCallback(() => {
8050
- onBlurProp == null ? void 0 : onBlurProp();
8051
- }, [onBlurProp]);
8058
+ }, [engine]);
8052
8059
  const handleKeyDown = useCallback(
8053
8060
  (e) => {
8054
8061
  var _a, _b, _c;
@@ -8400,6 +8407,7 @@ function EditorCore({
8400
8407
  if (!container) return;
8401
8408
  const selection = captureSelection(container);
8402
8409
  if (!selection) return;
8410
+ skipRestoreSelectionRef.current = true;
8403
8411
  const tr = createTransaction();
8404
8412
  tr.steps.push(
8405
8413
  tr_setSelection(selection)
@@ -8482,7 +8490,7 @@ function EditorCore({
8482
8490
  className: `editor-root relative flex flex-col ${className}`,
8483
8491
  style: editorHeight ? { minHeight: editorHeight } : void 0,
8484
8492
  children: [
8485
- !readOnly && !hideToolbar && /* @__PURE__ */ jsx(
8493
+ !readOnly && /* @__PURE__ */ jsx(
8486
8494
  Toolbar,
8487
8495
  {
8488
8496
  engine,
@@ -8493,8 +8501,7 @@ function EditorCore({
8493
8501
  linkPopupOpen,
8494
8502
  onLinkPopupClose: () => setLinkPopupOpen(false),
8495
8503
  isSourceMode,
8496
- onToggleSource: handleToggleSource,
8497
- toolbarConfig
8504
+ onToggleSource: handleToggleSource
8498
8505
  }
8499
8506
  ),
8500
8507
  /* @__PURE__ */ jsx("div", { className: "relative", children: isSourceMode ? /* @__PURE__ */ jsx(
@@ -8521,7 +8528,7 @@ function EditorCore({
8521
8528
  isVisualEmpty && /* @__PURE__ */ jsx(
8522
8529
  "div",
8523
8530
  {
8524
- className: "\n absolute\n top-6\n left-6\n pointer-events-none\n select-none\n text-base\n text-gray-400\n \n ",
8531
+ className: "\r\n absolute\r\n top-6\r\n left-6\r\n pointer-events-none\r\n select-none\r\n text-base\r\n text-gray-400\r\n dark:text-gray-500\r\n ",
8525
8532
  "aria-hidden": "true",
8526
8533
  children: placeholder
8527
8534
  }
@@ -8539,7 +8546,6 @@ function EditorCore({
8539
8546
  onMouseDown: handleMouseDown,
8540
8547
  onContextMenu: handleContextMenu,
8541
8548
  onFocus: handleFocus,
8542
- onBlur: handleBlur,
8543
8549
  onClick: handleClick,
8544
8550
  onKeyDown: handleKeyDown,
8545
8551
  className: [
@@ -8551,7 +8557,7 @@ function EditorCore({
8551
8557
  "text-base",
8552
8558
  "leading-relaxed",
8553
8559
  "text-gray-900",
8554
- "",
8560
+ "dark:text-gray-100",
8555
8561
  readOnly ? "cursor-default" : "cursor-text"
8556
8562
  ].join(" ")
8557
8563
  }
@@ -8561,7 +8567,7 @@ function EditorCore({
8561
8567
  {
8562
8568
  role: "tooltip",
8563
8569
  style: { left: linkTooltip.x, top: linkTooltip.y },
8564
- className: "absolute z-50 pointer-events-none flex items-center gap-1.5 bg-gray-900 text-white text-xs px-2.5 py-1.5 rounded shadow-lg max-w-xs",
8570
+ className: "absolute z-50 pointer-events-none flex items-center gap-1.5 bg-gray-900 dark:bg-gray-700 text-white text-xs px-2.5 py-1.5 rounded shadow-lg max-w-xs",
8565
8571
  children: [
8566
8572
  /* @__PURE__ */ jsxs("svg", { width: "11", height: "11", viewBox: "0 0 16 16", fill: "none", className: "shrink-0 opacity-70", "aria-hidden": "true", children: [
8567
8573
  /* @__PURE__ */ jsx("path", { d: "M6.5 3.5H4A2.5 2.5 0 0 0 4 8.5h2", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
@@ -8676,171 +8682,1023 @@ function prettyPrintHTML(html) {
8676
8682
  }
8677
8683
  return result.join("\n");
8678
8684
  }
8679
-
8680
- // src/editor/table/TablePlugin.ts
8681
- function getCellDOMElement(cellPathJson) {
8682
- return document.querySelector(`[data-block-path='${cellPathJson}']`);
8683
- }
8684
- function isCaretAtContainerStart(container) {
8685
- const sel = window.getSelection();
8686
- if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
8687
- const range = sel.getRangeAt(0);
8688
- if (range.startOffset !== 0) return false;
8689
- let node = range.startContainer;
8690
- while (node && node !== container) {
8691
- if (node.previousSibling) return false;
8692
- node = node.parentNode;
8693
- }
8694
- return node === container;
8695
- }
8696
- function isCaretAtContainerEnd(container) {
8697
- var _a, _b;
8698
- const sel = window.getSelection();
8699
- if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
8700
- const range = sel.getRangeAt(0);
8701
- const endNode = range.endContainer;
8702
- const endOffset = range.endOffset;
8703
- const len = endNode.nodeType === Node.TEXT_NODE ? (_b = (_a = endNode.textContent) == null ? void 0 : _a.length) != null ? _b : 0 : endNode.childNodes.length;
8704
- if (endOffset !== len) return false;
8705
- let node = endNode;
8706
- while (node && node !== container) {
8707
- if (node.nextSibling) return false;
8708
- node = node.parentNode;
8709
- }
8710
- return node === container;
8711
- }
8712
- function isCaretOnEdgeLine(container, direction) {
8713
- const sel = window.getSelection();
8714
- if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
8715
- const caretRange = sel.getRangeAt(0).cloneRange();
8716
- caretRange.collapse(true);
8717
- const caretRects = caretRange.getClientRects();
8718
- if (!caretRects.length) return true;
8719
- const caretRect = caretRects[0];
8720
- const containerRect = container.getBoundingClientRect();
8721
- const lineHeight = parseFloat(getComputedStyle(container).lineHeight) || 20;
8722
- if (direction === "up") {
8723
- return caretRect.top < containerRect.top + lineHeight;
8724
- } else {
8725
- return caretRect.bottom > containerRect.bottom - lineHeight;
8726
- }
8727
- }
8728
- var TablePlugin = {
8729
- name: "table",
8730
- keyBindings: {
8731
- Tab: (engine) => {
8732
- var _a;
8733
- const state = engine.getState();
8734
- const sel = state.selection;
8735
- if (!sel) return false;
8736
- const cellPos = findCellPosition(state.doc, sel.anchor.path);
8737
- if (!cellPos) return false;
8738
- const { tablePath, row, col } = cellPos;
8739
- const table = getNodeAtPath(state.doc, tablePath);
8740
- const { rows, cols } = getTableDimensions(table);
8741
- let nextRow = row;
8742
- let nextCol = col + 1;
8743
- while (nextRow < rows) {
8744
- if (nextCol >= cols) {
8745
- nextCol = 0;
8746
- nextRow++;
8747
- continue;
8748
- }
8749
- const c = getNodeAtPath(state.doc, [...tablePath, nextRow, nextCol]);
8750
- if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
8751
- nextCol++;
8752
- }
8753
- if (nextRow >= rows) {
8754
- const newTable = insertTableRowAfter(table, rows - 1);
8755
- const newChildren = [...state.doc.children];
8756
- newChildren[tablePath[0]] = newTable;
8757
- const tempDoc = { type: "doc", children: newChildren };
8758
- const tr2 = createTransaction();
8759
- tr2.steps.push({ type: "delete_node", path: tablePath });
8760
- tr2.steps.push({
8761
- type: "insert_node",
8762
- parentPath: tablePath.length > 1 ? tablePath.slice(0, -1) : [],
8763
- index: tablePath[tablePath.length - 1],
8764
- node: newTable
8765
- });
8766
- const pos2 = getCellFirstPosition(tempDoc, tablePath, rows, 0);
8767
- if (pos2) tr2.steps.push(tr_setSelection(makeCollapsedSelection(pos2)));
8768
- engine.dispatch(tr2);
8769
- return true;
8770
- }
8771
- const pos = getCellFirstPosition(state.doc, tablePath, nextRow, nextCol);
8772
- if (!pos) return false;
8773
- const tr = createTransaction();
8774
- tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8775
- engine.dispatch(tr);
8776
- return true;
8777
- },
8778
- "ArrowRight": (engine) => {
8779
- var _a;
8780
- const state = engine.getState();
8781
- const sel = state.selection;
8782
- if (!sel) return false;
8783
- const cellPos = findCellPosition(state.doc, sel.anchor.path);
8784
- if (!cellPos) return false;
8785
- const { tablePath, row, col } = cellPos;
8786
- const cellPath = [...tablePath, row, col];
8787
- const cellEl = getCellDOMElement(JSON.stringify(cellPath));
8788
- if (!cellEl || !isCaretAtContainerEnd(cellEl)) return false;
8789
- const table = getNodeAtPath(state.doc, tablePath);
8790
- const { rows, cols } = getTableDimensions(table);
8791
- let nextRow = row;
8792
- let nextCol = col + 1;
8793
- while (nextRow < rows) {
8794
- if (nextCol >= cols) {
8795
- nextCol = 0;
8796
- nextRow++;
8797
- continue;
8798
- }
8799
- const c = getNodeAtPath(state.doc, [...tablePath, nextRow, nextCol]);
8800
- if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
8801
- nextCol++;
8685
+ function EditorCore({
8686
+ engine,
8687
+ placeholder = "Start writing...",
8688
+ className = "",
8689
+ readOnly = false,
8690
+ onHTMLChange,
8691
+ onJSONChange,
8692
+ onOpenLinkPopup,
8693
+ onUploadImage: _onUploadImage,
8694
+ onFocus: onFocusProp,
8695
+ onBlur: onBlurProp,
8696
+ hideToolbar = false,
8697
+ toolbarConfig
8698
+ }) {
8699
+ const containerRef = useRef(null);
8700
+ const isRenderingRef = useRef(false);
8701
+ const scrollCaretIntoView = useCallback(() => {
8702
+ var _a, _b;
8703
+ const sel = window.getSelection();
8704
+ if (!sel || sel.rangeCount === 0) return;
8705
+ const range = sel.getRangeAt(0).cloneRange();
8706
+ range.collapse(true);
8707
+ const caretRect = range.getBoundingClientRect();
8708
+ let rect = caretRect;
8709
+ if (!rect || rect.top === 0 && rect.bottom === 0 && rect.left === 0) {
8710
+ const node = range.startContainer;
8711
+ const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
8712
+ if (el) rect = el.getBoundingClientRect();
8713
+ }
8714
+ if (!rect || rect.bottom === 0) return;
8715
+ let scrollEl = (_b = (_a = containerRef.current) == null ? void 0 : _a.parentElement) != null ? _b : null;
8716
+ while (scrollEl) {
8717
+ const style = window.getComputedStyle(scrollEl);
8718
+ const overflow = style.overflow + style.overflowY;
8719
+ if (/auto|scroll/.test(overflow)) break;
8720
+ scrollEl = scrollEl.parentElement;
8721
+ }
8722
+ const PADDING = 24;
8723
+ if (scrollEl) {
8724
+ const containerRect = scrollEl.getBoundingClientRect();
8725
+ if (rect.bottom > containerRect.bottom - PADDING) {
8726
+ scrollEl.scrollBy({ top: rect.bottom - containerRect.bottom + PADDING, behavior: "smooth" });
8727
+ } else if (rect.top < containerRect.top + PADDING) {
8728
+ scrollEl.scrollBy({ top: rect.top - containerRect.top - PADDING, behavior: "smooth" });
8802
8729
  }
8803
- if (nextRow >= rows) return true;
8804
- const pos = getCellFirstPosition(state.doc, tablePath, nextRow, nextCol);
8805
- if (!pos) return false;
8806
- const tr = createTransaction();
8807
- tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8808
- engine.dispatch(tr);
8809
- return true;
8810
- },
8811
- "ArrowLeft": (engine) => {
8812
- var _a;
8813
- const state = engine.getState();
8814
- const sel = state.selection;
8815
- if (!sel) return false;
8816
- const cellPos = findCellPosition(state.doc, sel.anchor.path);
8817
- if (!cellPos) return false;
8818
- const { tablePath, row, col } = cellPos;
8819
- const cellPath = [...tablePath, row, col];
8820
- const cellEl = getCellDOMElement(JSON.stringify(cellPath));
8821
- if (!cellEl || !isCaretAtContainerStart(cellEl)) return false;
8822
- const table = getNodeAtPath(state.doc, tablePath);
8823
- const { cols } = getTableDimensions(table);
8824
- let prevRow = row;
8825
- let prevCol = col - 1;
8826
- while (prevRow >= 0) {
8827
- if (prevCol < 0) {
8828
- prevCol = cols - 1;
8829
- prevRow--;
8830
- continue;
8831
- }
8832
- const c = getNodeAtPath(state.doc, [...tablePath, prevRow, prevCol]);
8833
- if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
8834
- prevCol--;
8730
+ } else {
8731
+ if (rect.bottom > window.innerHeight - PADDING) {
8732
+ window.scrollBy({ top: rect.bottom - window.innerHeight + PADDING, behavior: "smooth" });
8733
+ } else if (rect.top < PADDING) {
8734
+ window.scrollBy({ top: rect.top - PADDING, behavior: "smooth" });
8835
8735
  }
8836
- if (prevRow < 0) return true;
8837
- const pos = getCellLastPosition(state.doc, tablePath, prevRow, prevCol);
8838
- if (!pos) return false;
8736
+ }
8737
+ }, []);
8738
+ const isComposingRef = useRef(false);
8739
+ const stateRef = useRef(engine.getState());
8740
+ const [selectedImagePath, setSelectedImagePath] = useState(null);
8741
+ const [findReplaceOpen, setFindReplaceOpen] = useState(false);
8742
+ const [findReplaceMode, setFindReplaceMode] = useState("find");
8743
+ const [linkPopupOpen, setLinkPopupOpen] = useState(false);
8744
+ const [isSourceMode, setIsSourceMode] = useState(false);
8745
+ const [sourceHTML, setSourceHTML] = useState("");
8746
+ const [editorHeight, setEditorHeight] = useState(null);
8747
+ const editorRootRef = useRef(null);
8748
+ const resizeDragRef = useRef(null);
8749
+ const [linkTooltip, setLinkTooltip] = useState(null);
8750
+ const linkTooltipTimerRef = useRef(null);
8751
+ const handleToggleSource = useCallback(() => {
8752
+ if (!isSourceMode) {
8753
+ const html = htmlSerializer.serialize(engine.getState().doc);
8754
+ setSourceHTML(prettyPrintHTML2(html));
8755
+ setIsSourceMode(true);
8756
+ } else {
8757
+ const newDoc = htmlSerializer.deserialize(sourceHTML);
8839
8758
  const tr = createTransaction();
8840
- tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
8759
+ tr.steps.push(tr_replaceDoc(newDoc));
8841
8760
  engine.dispatch(tr);
8842
- return true;
8843
- },
8761
+ setIsSourceMode(false);
8762
+ }
8763
+ }, [isSourceMode, sourceHTML, engine]);
8764
+ useEffect(() => {
8765
+ const onMouseMove = (e) => {
8766
+ const drag = resizeDragRef.current;
8767
+ if (!drag) return;
8768
+ const newHeight = Math.max(120, drag.startHeight + (e.clientY - drag.startY));
8769
+ setEditorHeight(newHeight);
8770
+ };
8771
+ const onMouseUp = () => {
8772
+ resizeDragRef.current = null;
8773
+ };
8774
+ document.addEventListener("mousemove", onMouseMove);
8775
+ document.addEventListener("mouseup", onMouseUp);
8776
+ return () => {
8777
+ document.removeEventListener("mousemove", onMouseMove);
8778
+ document.removeEventListener("mouseup", onMouseUp);
8779
+ };
8780
+ }, []);
8781
+ const [tableContextMenu, setTableContextMenu] = useState(null);
8782
+ const [tableSelection, setTableSelection] = useState(null);
8783
+ const cellDragRef = useRef(null);
8784
+ const colResizeRef = useRef(null);
8785
+ const state = useEditorState(engine);
8786
+ const inTableCellPos = state.selection ? findCellPosition(state.doc, state.selection.anchor.path) : null;
8787
+ useLayoutEffect(() => {
8788
+ stateRef.current = state;
8789
+ }, [state]);
8790
+ useLayoutEffect(() => {
8791
+ const container = containerRef.current;
8792
+ if (!container) return;
8793
+ isRenderingRef.current = true;
8794
+ renderDocument(state.doc, container);
8795
+ if (!container.contains(document.activeElement)) {
8796
+ container.focus({ preventScroll: true });
8797
+ }
8798
+ if (state.selection) {
8799
+ restoreSelection(container, state.selection);
8800
+ scrollCaretIntoView();
8801
+ }
8802
+ isRenderingRef.current = false;
8803
+ if (onHTMLChange) {
8804
+ onHTMLChange(htmlSerializer.serialize(state.doc));
8805
+ }
8806
+ if (onJSONChange) {
8807
+ onJSONChange(jsonSerializer.serialize(state.doc));
8808
+ }
8809
+ }, [state.doc]);
8810
+ useLayoutEffect(() => {
8811
+ const container = containerRef.current;
8812
+ if (!container || !state.selection) return;
8813
+ restoreSelection(container, state.selection);
8814
+ scrollCaretIntoView();
8815
+ }, [state.selection]);
8816
+ useEffect(() => {
8817
+ const container = containerRef.current;
8818
+ if (!container || readOnly) return;
8819
+ return attachClipboardHandlers(container, engine);
8820
+ }, [engine, readOnly]);
8821
+ useEffect(() => {
8822
+ const container = containerRef.current;
8823
+ if (!container) return;
8824
+ container.querySelectorAll(".editor-cell-selected").forEach((el) => {
8825
+ el.classList.remove("editor-cell-selected");
8826
+ });
8827
+ if (!tableSelection) return;
8828
+ const { tablePath, anchorCell, focusCell } = tableSelection;
8829
+ const minRow = Math.min(anchorCell[0], focusCell[0]);
8830
+ const maxRow = Math.max(anchorCell[0], focusCell[0]);
8831
+ const minCol = Math.min(anchorCell[1], focusCell[1]);
8832
+ const maxCol = Math.max(anchorCell[1], focusCell[1]);
8833
+ for (let r = minRow; r <= maxRow; r++) {
8834
+ for (let c = minCol; c <= maxCol; c++) {
8835
+ const cellPath = [...tablePath, r, c];
8836
+ const td = container.querySelector(
8837
+ `[data-block-path="${JSON.stringify(cellPath)}"]`
8838
+ );
8839
+ if (td) td.classList.add("editor-cell-selected");
8840
+ }
8841
+ }
8842
+ }, [tableSelection, state.doc]);
8843
+ useEffect(() => {
8844
+ const container = containerRef.current;
8845
+ if (!container) return;
8846
+ container.querySelectorAll(".editor-cell-active").forEach((el) => {
8847
+ el.classList.remove("editor-cell-active");
8848
+ });
8849
+ if (!inTableCellPos) {
8850
+ setTableSelection(null);
8851
+ return;
8852
+ }
8853
+ if (tableSelection) {
8854
+ const { anchorCell, focusCell, tablePath } = tableSelection;
8855
+ const sameTable = JSON.stringify(tablePath) === JSON.stringify(inTableCellPos.tablePath);
8856
+ const singleCell = anchorCell[0] === focusCell[0] && anchorCell[1] === focusCell[1];
8857
+ if (!sameTable || singleCell) {
8858
+ setTableSelection(null);
8859
+ }
8860
+ }
8861
+ const cellPath = [...inTableCellPos.tablePath, inTableCellPos.row, inTableCellPos.col];
8862
+ const td = container.querySelector(
8863
+ `[data-block-path="${JSON.stringify(cellPath)}"]`
8864
+ );
8865
+ if (td) td.classList.add("editor-cell-active");
8866
+ }, [inTableCellPos, state.doc]);
8867
+ useEffect(() => {
8868
+ const onSelectionChange = () => {
8869
+ if (isRenderingRef.current) return;
8870
+ const container = containerRef.current;
8871
+ if (!container) return;
8872
+ if (!isSelectionInContainer(container)) return;
8873
+ const captured = captureSelection(container);
8874
+ if (!captured) return;
8875
+ const currentSelection = engine.getState().selection;
8876
+ if (currentSelection && JSON.stringify(currentSelection.anchor) === JSON.stringify(captured.anchor) && JSON.stringify(currentSelection.focus) === JSON.stringify(captured.focus)) {
8877
+ return;
8878
+ }
8879
+ const tr = createTransaction();
8880
+ tr.steps.push(tr_setSelection(captured));
8881
+ engine.dispatch(tr);
8882
+ };
8883
+ document.addEventListener(
8884
+ "selectionchange",
8885
+ onSelectionChange
8886
+ );
8887
+ return () => {
8888
+ document.removeEventListener(
8889
+ "selectionchange",
8890
+ onSelectionChange
8891
+ );
8892
+ };
8893
+ }, [engine]);
8894
+ const handleFocus = useCallback(() => {
8895
+ const currentState = engine.getState();
8896
+ if (!currentState.selection) {
8897
+ const tr = createTransaction();
8898
+ tr.steps.push(
8899
+ tr_setSelection(
8900
+ makeCollapsedSelection({ path: [0], offset: 0 })
8901
+ )
8902
+ );
8903
+ engine.dispatch(tr);
8904
+ }
8905
+ onFocusProp == null ? void 0 : onFocusProp();
8906
+ }, [engine, onFocusProp]);
8907
+ const handleBlur = useCallback(() => {
8908
+ onBlurProp == null ? void 0 : onBlurProp();
8909
+ }, [onBlurProp]);
8910
+ const handleKeyDown = useCallback(
8911
+ (e) => {
8912
+ var _a, _b, _c;
8913
+ if (readOnly) return;
8914
+ if ((e.ctrlKey || e.metaKey) && e.key === "f") {
8915
+ e.preventDefault();
8916
+ setFindReplaceMode("find");
8917
+ setFindReplaceOpen(true);
8918
+ return;
8919
+ }
8920
+ if ((e.ctrlKey || e.metaKey) && e.key === "h") {
8921
+ e.preventDefault();
8922
+ setFindReplaceMode("replace");
8923
+ setFindReplaceOpen(true);
8924
+ return;
8925
+ }
8926
+ if ((e.ctrlKey || e.metaKey) && e.key === "k") {
8927
+ e.preventDefault();
8928
+ setLinkPopupOpen(true);
8929
+ onOpenLinkPopup == null ? void 0 : onOpenLinkPopup();
8930
+ return;
8931
+ }
8932
+ if ((e.ctrlKey || e.metaKey) && (e.key === "a" || e.key === "A")) {
8933
+ e.preventDefault();
8934
+ const container = containerRef.current;
8935
+ if (container) {
8936
+ const spans = container.querySelectorAll("[data-path]");
8937
+ const first = spans[0];
8938
+ const last = spans[spans.length - 1];
8939
+ if (first && last) {
8940
+ const sel = window.getSelection();
8941
+ const range = document.createRange();
8942
+ range.setStart((_a = leafTextNode2(first, "first")) != null ? _a : first, 0);
8943
+ const lastLeaf = leafTextNode2(last, "last");
8944
+ range.setEnd(lastLeaf != null ? lastLeaf : last, (_c = (_b = lastLeaf == null ? void 0 : lastLeaf.textContent) == null ? void 0 : _b.length) != null ? _c : 0);
8945
+ sel == null ? void 0 : sel.removeAllRanges();
8946
+ sel == null ? void 0 : sel.addRange(range);
8947
+ }
8948
+ }
8949
+ return;
8950
+ }
8951
+ if (selectedImagePath && (e.key === "Delete" || e.key === "Backspace")) {
8952
+ e.preventDefault();
8953
+ deleteImageAtPath(selectedImagePath)(engine);
8954
+ setSelectedImagePath(null);
8955
+ return;
8956
+ }
8957
+ if (selectedImagePath) setSelectedImagePath(null);
8958
+ if (e.key === "Tab") {
8959
+ const { doc, selection: sel } = engine.getState();
8960
+ if (sel) {
8961
+ const bp = findContentBlockPath(doc, sel.anchor.path);
8962
+ const block = bp ? getNodeAtPath(doc, bp) : null;
8963
+ if ((block == null ? void 0 : block.type) === "code_block") {
8964
+ e.preventDefault();
8965
+ insertText(" ")(engine);
8966
+ return;
8967
+ }
8968
+ if ((block == null ? void 0 : block.type) === "list_item") {
8969
+ e.preventDefault();
8970
+ if (e.shiftKey) {
8971
+ outdentListItem(engine);
8972
+ } else {
8973
+ indentListItem(engine);
8974
+ }
8975
+ return;
8976
+ }
8977
+ if (findCellPosition(doc, sel.anchor.path)) {
8978
+ e.preventDefault();
8979
+ }
8980
+ }
8981
+ }
8982
+ const handled = engine.handleKeyDown(
8983
+ e.nativeEvent
8984
+ );
8985
+ if (handled) return;
8986
+ if (e.key === "Enter" && !e.shiftKey) {
8987
+ e.preventDefault();
8988
+ handleEnter(engine);
8989
+ return;
8990
+ }
8991
+ if (e.key === "Backspace") {
8992
+ e.preventDefault();
8993
+ handleBackspace(engine);
8994
+ return;
8995
+ }
8996
+ if (e.key === "Delete") {
8997
+ e.preventDefault();
8998
+ handleDelete(engine);
8999
+ return;
9000
+ }
9001
+ if (e.key === "Enter" && e.shiftKey) {
9002
+ e.preventDefault();
9003
+ insertText("\n")(engine);
9004
+ return;
9005
+ }
9006
+ },
9007
+ [engine, readOnly, onOpenLinkPopup, selectedImagePath]
9008
+ );
9009
+ useEffect(() => {
9010
+ const container = containerRef.current;
9011
+ if (!container || readOnly) return;
9012
+ const onBeforeInput = (e) => {
9013
+ if (e.isComposing || isComposingRef.current) return;
9014
+ switch (e.inputType) {
9015
+ case "insertText":
9016
+ case "insertReplacementText": {
9017
+ if (!e.data) return;
9018
+ e.preventDefault();
9019
+ insertText(e.data)(engine);
9020
+ return;
9021
+ }
9022
+ case "insertParagraph": {
9023
+ e.preventDefault();
9024
+ handleEnter(engine);
9025
+ return;
9026
+ }
9027
+ case "insertLineBreak": {
9028
+ e.preventDefault();
9029
+ insertText("\n")(engine);
9030
+ return;
9031
+ }
9032
+ case "deleteContentBackward":
9033
+ case "deleteWordBackward":
9034
+ case "deleteSoftLineBackward": {
9035
+ e.preventDefault();
9036
+ handleBackspace(engine);
9037
+ return;
9038
+ }
9039
+ case "insertFromPaste":
9040
+ case "insertFromDrop":
9041
+ return;
9042
+ default:
9043
+ e.preventDefault();
9044
+ }
9045
+ };
9046
+ container.addEventListener("beforeinput", onBeforeInput);
9047
+ return () => container.removeEventListener("beforeinput", onBeforeInput);
9048
+ }, [engine, readOnly]);
9049
+ useEffect(() => {
9050
+ const container = containerRef.current;
9051
+ if (!container || readOnly) return;
9052
+ const onCompositionStart = () => {
9053
+ isComposingRef.current = true;
9054
+ };
9055
+ const onCompositionEnd = (e) => {
9056
+ isComposingRef.current = false;
9057
+ if (e.data) insertText(e.data)(engine);
9058
+ };
9059
+ container.addEventListener("compositionstart", onCompositionStart);
9060
+ container.addEventListener("compositionend", onCompositionEnd);
9061
+ return () => {
9062
+ container.removeEventListener("compositionstart", onCompositionStart);
9063
+ container.removeEventListener("compositionend", onCompositionEnd);
9064
+ };
9065
+ }, [engine, readOnly]);
9066
+ useEffect(() => {
9067
+ const container = containerRef.current;
9068
+ if (!container) return;
9069
+ const onMouseOver = (e) => {
9070
+ var _a;
9071
+ const anchor = e.target.closest("a.editor-link");
9072
+ if (!anchor) return;
9073
+ const href = (_a = anchor.getAttribute("href")) != null ? _a : "";
9074
+ if (!href) return;
9075
+ if (linkTooltipTimerRef.current) clearTimeout(linkTooltipTimerRef.current);
9076
+ linkTooltipTimerRef.current = setTimeout(() => {
9077
+ const rect = anchor.getBoundingClientRect();
9078
+ const containerRect = container.getBoundingClientRect();
9079
+ setLinkTooltip({
9080
+ href,
9081
+ x: rect.left - containerRect.left,
9082
+ y: rect.bottom - containerRect.top + 6
9083
+ });
9084
+ }, 200);
9085
+ };
9086
+ const onMouseOut = (e) => {
9087
+ const anchor = e.target.closest("a.editor-link");
9088
+ if (!anchor) return;
9089
+ if (linkTooltipTimerRef.current) clearTimeout(linkTooltipTimerRef.current);
9090
+ setLinkTooltip(null);
9091
+ };
9092
+ const onClick = (e) => {
9093
+ if (!(e.ctrlKey || e.metaKey)) return;
9094
+ const anchor = e.target.closest("a.editor-link");
9095
+ if (!anchor) return;
9096
+ e.preventDefault();
9097
+ const href = anchor.getAttribute("href");
9098
+ if (href) window.open(href, "_blank", "noopener,noreferrer");
9099
+ };
9100
+ container.addEventListener("mouseover", onMouseOver);
9101
+ container.addEventListener("mouseout", onMouseOut);
9102
+ container.addEventListener("click", onClick);
9103
+ return () => {
9104
+ container.removeEventListener("mouseover", onMouseOver);
9105
+ container.removeEventListener("mouseout", onMouseOut);
9106
+ container.removeEventListener("click", onClick);
9107
+ if (linkTooltipTimerRef.current) clearTimeout(linkTooltipTimerRef.current);
9108
+ };
9109
+ }, []);
9110
+ useEffect(() => {
9111
+ const onMouseMove = (e) => {
9112
+ const drag = colResizeRef.current;
9113
+ if (!drag) return;
9114
+ const delta = e.clientX - drag.startX;
9115
+ const newWidth = Math.max(40, drag.startWidth + delta);
9116
+ const container = containerRef.current;
9117
+ if (container) {
9118
+ const colEl = container.querySelector(
9119
+ `col[data-col-index="${drag.colIndex}"]`
9120
+ );
9121
+ if (colEl) colEl.style.width = `${newWidth}px`;
9122
+ }
9123
+ };
9124
+ const onMouseUp = (e) => {
9125
+ const drag = colResizeRef.current;
9126
+ if (!drag) return;
9127
+ const delta = e.clientX - drag.startX;
9128
+ const newWidth = Math.max(40, drag.startWidth + delta);
9129
+ colResizeRef.current = null;
9130
+ setColumnWidth(drag.tablePath, drag.colIndex, newWidth)(engine);
9131
+ };
9132
+ document.addEventListener("mousemove", onMouseMove);
9133
+ document.addEventListener("mouseup", onMouseUp);
9134
+ return () => {
9135
+ document.removeEventListener("mousemove", onMouseMove);
9136
+ document.removeEventListener("mouseup", onMouseUp);
9137
+ };
9138
+ }, [engine]);
9139
+ useEffect(() => {
9140
+ const onMouseMove = (e) => {
9141
+ var _a, _b;
9142
+ const drag = cellDragRef.current;
9143
+ if (!drag) return;
9144
+ const td = e.target.closest("[data-cell-row]");
9145
+ if (!td) return;
9146
+ const row = parseInt((_a = td.dataset.cellRow) != null ? _a : "0");
9147
+ const col = parseInt((_b = td.dataset.cellCol) != null ? _b : "0");
9148
+ const tdTablePath = td.dataset.cellTablePath ? JSON.parse(td.dataset.cellTablePath) : null;
9149
+ if (!tdTablePath || JSON.stringify(tdTablePath) !== JSON.stringify(drag.tablePath)) return;
9150
+ setTableSelection({
9151
+ tablePath: drag.tablePath,
9152
+ anchorCell: [drag.startRow, drag.startCol],
9153
+ focusCell: [row, col]
9154
+ });
9155
+ };
9156
+ const onMouseUp = () => {
9157
+ cellDragRef.current = null;
9158
+ };
9159
+ document.addEventListener("mousemove", onMouseMove);
9160
+ document.addEventListener("mouseup", onMouseUp);
9161
+ return () => {
9162
+ document.removeEventListener("mousemove", onMouseMove);
9163
+ document.removeEventListener("mouseup", onMouseUp);
9164
+ };
9165
+ }, []);
9166
+ const handleContextMenu = useCallback(
9167
+ (e) => {
9168
+ var _a, _b, _c, _d, _e, _f;
9169
+ const td = e.target.closest("[data-cell-row]");
9170
+ if (!td) return;
9171
+ e.preventDefault();
9172
+ const row = parseInt((_a = td.dataset.cellRow) != null ? _a : "0");
9173
+ const col = parseInt((_b = td.dataset.cellCol) != null ? _b : "0");
9174
+ const tablePath = td.dataset.cellTablePath ? JSON.parse(td.dataset.cellTablePath) : null;
9175
+ if (!tablePath) return;
9176
+ const cellNode = getNodeAtPath(
9177
+ engine.getState().doc,
9178
+ [...tablePath, row, col]
9179
+ );
9180
+ const isMerged = !!cellNode && (((_d = (_c = cellNode.attrs) == null ? void 0 : _c.colspan) != null ? _d : 1) > 1 || ((_f = (_e = cellNode.attrs) == null ? void 0 : _e.rowspan) != null ? _f : 1) > 1);
9181
+ setTableContextMenu({ x: e.clientX, y: e.clientY, tablePath, row, col, isMerged });
9182
+ },
9183
+ [engine]
9184
+ );
9185
+ const handleMouseDown = useCallback(
9186
+ (e) => {
9187
+ var _a, _b, _c, _d;
9188
+ if (readOnly) return;
9189
+ const target = e.target;
9190
+ if (target.dataset.resizeImagePath) {
9191
+ e.preventDefault();
9192
+ const imagePath = JSON.parse(target.dataset.resizeImagePath);
9193
+ const corner = (_a = target.dataset.resizeImagePos) != null ? _a : "se";
9194
+ const container = containerRef.current;
9195
+ let startWidth = 300;
9196
+ if (container) {
9197
+ const fig2 = container.querySelector(`[data-image-path="${JSON.stringify(imagePath)}"]`);
9198
+ const img = fig2 == null ? void 0 : fig2.querySelector("img");
9199
+ if (img) startWidth = img.getBoundingClientRect().width || 300;
9200
+ }
9201
+ imageResizeRef.current = {
9202
+ imagePath,
9203
+ corner,
9204
+ startX: e.clientX,
9205
+ startY: e.clientY,
9206
+ startWidth,
9207
+ startHeight: 0
9208
+ };
9209
+ return;
9210
+ }
9211
+ const fig = target.closest("[data-image-path]");
9212
+ if (fig == null ? void 0 : fig.dataset.imagePath) {
9213
+ e.preventDefault();
9214
+ setSelectedImagePath(JSON.parse(fig.dataset.imagePath));
9215
+ return;
9216
+ }
9217
+ setSelectedImagePath(null);
9218
+ if (target.dataset.resizeTable) {
9219
+ e.preventDefault();
9220
+ const tablePath = JSON.parse(target.dataset.resizeTable);
9221
+ const colIndex = parseInt((_b = target.dataset.resizeCol) != null ? _b : "0");
9222
+ const container = containerRef.current;
9223
+ let startWidth = 120;
9224
+ if (container) {
9225
+ const colEl = container.querySelector(
9226
+ `col[data-col-index="${colIndex}"]`
9227
+ );
9228
+ if (colEl) startWidth = colEl.getBoundingClientRect().width || 120;
9229
+ }
9230
+ colResizeRef.current = { tablePath, colIndex, startX: e.clientX, startWidth };
9231
+ return;
9232
+ }
9233
+ const td = target.closest("[data-cell-row]");
9234
+ if (!td) setTableSelection(null);
9235
+ if (td && !readOnly) {
9236
+ const row = parseInt((_c = td.dataset.cellRow) != null ? _c : "0");
9237
+ const col = parseInt((_d = td.dataset.cellCol) != null ? _d : "0");
9238
+ const tdTablePath = td.dataset.cellTablePath ? JSON.parse(td.dataset.cellTablePath) : null;
9239
+ if (tdTablePath) {
9240
+ cellDragRef.current = { tablePath: tdTablePath, startRow: row, startCol: col };
9241
+ setTableSelection({
9242
+ tablePath: tdTablePath,
9243
+ anchorCell: [row, col],
9244
+ focusCell: [row, col]
9245
+ });
9246
+ }
9247
+ }
9248
+ const checkTarget = target.dataset.checkPath ? target : target.closest("[data-check-path]");
9249
+ if (checkTarget == null ? void 0 : checkTarget.dataset.checkPath) {
9250
+ e.preventDefault();
9251
+ toggleCheckItemAt(JSON.parse(checkTarget.dataset.checkPath))(engine);
9252
+ }
9253
+ },
9254
+ [engine, readOnly]
9255
+ );
9256
+ const handleClick = useCallback(() => {
9257
+ const container = containerRef.current;
9258
+ if (!container) return;
9259
+ const selection = captureSelection(container);
9260
+ if (!selection) return;
9261
+ const tr = createTransaction();
9262
+ tr.steps.push(
9263
+ tr_setSelection(selection)
9264
+ );
9265
+ engine.dispatch(tr);
9266
+ }, [engine]);
9267
+ const imageResizeRef = useRef(null);
9268
+ useEffect(() => {
9269
+ const onMouseMove = (e) => {
9270
+ const drag = imageResizeRef.current;
9271
+ if (!drag) return;
9272
+ const deltaX = e.clientX - drag.startX;
9273
+ const isRight = drag.corner === "ne" || drag.corner === "se";
9274
+ const newWidth = Math.max(60, drag.startWidth + (isRight ? deltaX : -deltaX));
9275
+ const container = containerRef.current;
9276
+ if (container) {
9277
+ const fig = container.querySelector(
9278
+ `[data-image-path="${JSON.stringify(drag.imagePath)}"]`
9279
+ );
9280
+ const img = fig == null ? void 0 : fig.querySelector("img");
9281
+ if (img) img.style.width = `${newWidth}px`;
9282
+ }
9283
+ };
9284
+ const onMouseUp = (e) => {
9285
+ const drag = imageResizeRef.current;
9286
+ if (!drag) return;
9287
+ const deltaX = e.clientX - drag.startX;
9288
+ const isRight = drag.corner === "ne" || drag.corner === "se";
9289
+ const newWidth = Math.max(60, drag.startWidth + (isRight ? deltaX : -deltaX));
9290
+ imageResizeRef.current = null;
9291
+ setImageAttr(drag.imagePath, { width: newWidth })(engine);
9292
+ };
9293
+ document.addEventListener("mousemove", onMouseMove);
9294
+ document.addEventListener("mouseup", onMouseUp);
9295
+ return () => {
9296
+ document.removeEventListener("mousemove", onMouseMove);
9297
+ document.removeEventListener("mouseup", onMouseUp);
9298
+ };
9299
+ }, [engine]);
9300
+ useEffect(() => {
9301
+ const container = containerRef.current;
9302
+ if (!container) return;
9303
+ container.querySelectorAll("[data-image-path]").forEach((el) => {
9304
+ el.removeAttribute("data-selected");
9305
+ });
9306
+ if (selectedImagePath) {
9307
+ const fig = container.querySelector(
9308
+ `[data-image-path="${JSON.stringify(selectedImagePath)}"]`
9309
+ );
9310
+ if (fig) fig.setAttribute("data-selected", "true");
9311
+ }
9312
+ }, [selectedImagePath, state.doc]);
9313
+ useEffect(() => {
9314
+ const container = containerRef.current;
9315
+ if (!container || readOnly) return;
9316
+ const onDragOver = (e) => {
9317
+ var _a;
9318
+ if ((_a = e.dataTransfer) == null ? void 0 : _a.types.includes("Files")) e.preventDefault();
9319
+ };
9320
+ const onDrop = (e) => {
9321
+ var _a;
9322
+ e.preventDefault();
9323
+ const file = (_a = e.dataTransfer) == null ? void 0 : _a.files[0];
9324
+ if (!file || !file.type.startsWith("image/")) return;
9325
+ const blobUrl = URL.createObjectURL(file);
9326
+ insertImage(blobUrl, file.name.replace(/\.[^.]+$/, ""))(engine);
9327
+ };
9328
+ container.addEventListener("dragover", onDragOver);
9329
+ container.addEventListener("drop", onDrop);
9330
+ return () => {
9331
+ container.removeEventListener("dragover", onDragOver);
9332
+ container.removeEventListener("drop", onDrop);
9333
+ };
9334
+ }, [engine, readOnly]);
9335
+ const isVisualEmpty = state.doc.children.length === 1 && state.doc.children[0].type === "paragraph" && state.doc.children[0].children.length === 0;
9336
+ return /* @__PURE__ */ jsxs(
9337
+ "div",
9338
+ {
9339
+ ref: editorRootRef,
9340
+ className: `editor-root relative flex flex-col ${className}`,
9341
+ style: editorHeight ? { minHeight: editorHeight } : void 0,
9342
+ children: [
9343
+ !readOnly && !hideToolbar && /* @__PURE__ */ jsx(
9344
+ Toolbar,
9345
+ {
9346
+ engine,
9347
+ onFindReplace: (mode) => {
9348
+ setFindReplaceMode(mode);
9349
+ setFindReplaceOpen(true);
9350
+ },
9351
+ linkPopupOpen,
9352
+ onLinkPopupClose: () => setLinkPopupOpen(false),
9353
+ isSourceMode,
9354
+ onToggleSource: handleToggleSource,
9355
+ toolbarConfig
9356
+ }
9357
+ ),
9358
+ /* @__PURE__ */ jsx("div", { className: "relative", children: isSourceMode ? /* @__PURE__ */ jsx(
9359
+ "textarea",
9360
+ {
9361
+ "aria-label": "HTML source editor",
9362
+ value: sourceHTML,
9363
+ onChange: (e) => {
9364
+ setSourceHTML(e.target.value);
9365
+ const t = e.target;
9366
+ t.style.height = "auto";
9367
+ t.style.height = t.scrollHeight + "px";
9368
+ },
9369
+ ref: (el) => {
9370
+ if (el) {
9371
+ el.style.height = "auto";
9372
+ el.style.height = el.scrollHeight + "px";
9373
+ }
9374
+ },
9375
+ spellCheck: false,
9376
+ className: "w-full block px-6 py-4 font-mono text-sm leading-relaxed bg-gray-950 text-green-300 outline-none resize-none border-0 overflow-hidden"
9377
+ }
9378
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
9379
+ isVisualEmpty && /* @__PURE__ */ jsx(
9380
+ "div",
9381
+ {
9382
+ className: "\r\n absolute\r\n top-6\r\n left-6\r\n pointer-events-none\r\n select-none\r\n text-base\r\n text-gray-400\r\n \r\n ",
9383
+ "aria-hidden": "true",
9384
+ children: placeholder
9385
+ }
9386
+ ),
9387
+ /* @__PURE__ */ jsx(
9388
+ "div",
9389
+ {
9390
+ ref: containerRef,
9391
+ contentEditable: !readOnly,
9392
+ suppressContentEditableWarning: true,
9393
+ role: "textbox",
9394
+ "aria-multiline": "true",
9395
+ "aria-label": "Rich text editor",
9396
+ spellCheck: true,
9397
+ onMouseDown: handleMouseDown,
9398
+ onContextMenu: handleContextMenu,
9399
+ onFocus: handleFocus,
9400
+ onBlur: handleBlur,
9401
+ onClick: handleClick,
9402
+ onKeyDown: handleKeyDown,
9403
+ className: [
9404
+ "editor-canvas",
9405
+ "min-h-[300px]",
9406
+ "px-6",
9407
+ "py-4",
9408
+ "outline-none",
9409
+ "text-base",
9410
+ "leading-relaxed",
9411
+ "text-gray-900",
9412
+ "",
9413
+ readOnly ? "cursor-default" : "cursor-text"
9414
+ ].join(" ")
9415
+ }
9416
+ ),
9417
+ linkTooltip && /* @__PURE__ */ jsxs(
9418
+ "div",
9419
+ {
9420
+ role: "tooltip",
9421
+ style: { left: linkTooltip.x, top: linkTooltip.y },
9422
+ className: "absolute z-50 pointer-events-none flex items-center gap-1.5 bg-gray-900 text-white text-xs px-2.5 py-1.5 rounded shadow-lg max-w-xs",
9423
+ children: [
9424
+ /* @__PURE__ */ jsxs("svg", { width: "11", height: "11", viewBox: "0 0 16 16", fill: "none", className: "shrink-0 opacity-70", "aria-hidden": "true", children: [
9425
+ /* @__PURE__ */ jsx("path", { d: "M6.5 3.5H4A2.5 2.5 0 0 0 4 8.5h2", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
9426
+ /* @__PURE__ */ jsx("path", { d: "M9.5 3.5H12A2.5 2.5 0 0 1 12 8.5h-2", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }),
9427
+ /* @__PURE__ */ jsx("path", { d: "M5.5 6h5", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
9428
+ ] }),
9429
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: linkTooltip.href }),
9430
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 opacity-50 ml-1", children: "Ctrl+click to open" })
9431
+ ]
9432
+ }
9433
+ )
9434
+ ] }) }),
9435
+ inTableCellPos && !readOnly && /* @__PURE__ */ jsx(
9436
+ TableToolbar,
9437
+ {
9438
+ engine,
9439
+ tablePath: inTableCellPos.tablePath,
9440
+ cellPos: { row: inTableCellPos.row, col: inTableCellPos.col },
9441
+ tableSelection,
9442
+ editorContainer: containerRef
9443
+ }
9444
+ ),
9445
+ tableContextMenu && /* @__PURE__ */ jsx(
9446
+ TableContextMenu,
9447
+ {
9448
+ x: tableContextMenu.x,
9449
+ y: tableContextMenu.y,
9450
+ tablePath: tableContextMenu.tablePath,
9451
+ row: tableContextMenu.row,
9452
+ col: tableContextMenu.col,
9453
+ isMerged: tableContextMenu.isMerged,
9454
+ tableSelection,
9455
+ engine,
9456
+ onClose: () => setTableContextMenu(null)
9457
+ }
9458
+ ),
9459
+ findReplaceOpen && /* @__PURE__ */ jsx(
9460
+ FindReplaceModal,
9461
+ {
9462
+ engine,
9463
+ editorContainer: containerRef,
9464
+ initialMode: findReplaceMode,
9465
+ onClose: () => setFindReplaceOpen(false)
9466
+ }
9467
+ ),
9468
+ selectedImagePath && !readOnly && /* @__PURE__ */ jsx(
9469
+ ImageToolbar,
9470
+ {
9471
+ engine,
9472
+ imagePath: selectedImagePath,
9473
+ editorContainer: containerRef,
9474
+ onClose: () => setSelectedImagePath(null)
9475
+ }
9476
+ ),
9477
+ !readOnly && /* @__PURE__ */ jsx(
9478
+ "div",
9479
+ {
9480
+ title: "Drag to resize",
9481
+ onMouseDown: (e) => {
9482
+ e.preventDefault();
9483
+ const rootEl = editorRootRef.current;
9484
+ if (!rootEl) return;
9485
+ resizeDragRef.current = {
9486
+ startY: e.clientY,
9487
+ startHeight: rootEl.getBoundingClientRect().height
9488
+ };
9489
+ },
9490
+ className: "absolute bottom-0 right-0 w-4 h-4 cursor-s-resize flex items-end justify-end pr-0.5 pb-0.5 select-none",
9491
+ "aria-hidden": "true",
9492
+ children: /* @__PURE__ */ jsx("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M7 1L1 7M7 4L4 7M7 7L7 7", stroke: "#9ca3af", strokeWidth: "1.2", strokeLinecap: "round" }) })
9493
+ }
9494
+ )
9495
+ ]
9496
+ }
9497
+ );
9498
+ }
9499
+ var BLOCK_TAGS2 = "p|h[1-6]|ul|ol|li|blockquote|pre|figure|table|thead|tbody|tr|th|td";
9500
+ var _OPEN_ONLY_RE2 = new RegExp(`^<(${BLOCK_TAGS2})(?:\\s[^>]*)?>$`, "i");
9501
+ var _CLOSE_ONLY_RE2 = new RegExp(`^<\\/(${BLOCK_TAGS2})>$`, "i");
9502
+ var _HR_RE2 = /^<hr(\s[^>]*)?\/?>$/i;
9503
+ function leafTextNode2(el, end) {
9504
+ let node = el;
9505
+ while (node.hasChildNodes()) {
9506
+ node = end === "first" ? node.firstChild : node.lastChild;
9507
+ }
9508
+ return node.nodeType === Node.TEXT_NODE ? node : null;
9509
+ }
9510
+ function prettyPrintHTML2(html) {
9511
+ const blockOpen = new RegExp(`(<(?:${BLOCK_TAGS2})(?:\\s[^>]*)?>)`, "gi");
9512
+ const blockClose = new RegExp(`(<\\/(?:${BLOCK_TAGS2})>)`, "gi");
9513
+ const spread = html.replace(blockOpen, "\n$1").replace(blockClose, "$1\n").replace(/<hr(\s[^>]*)?\/?>(\s*)/gi, "\n<hr />\n");
9514
+ let depth = 0;
9515
+ const result = [];
9516
+ for (const raw of spread.split("\n")) {
9517
+ const line = raw.trim();
9518
+ if (!line) continue;
9519
+ if (_HR_RE2.test(line)) {
9520
+ result.push(" ".repeat(depth) + line);
9521
+ continue;
9522
+ }
9523
+ if (_CLOSE_ONLY_RE2.test(line)) {
9524
+ depth = Math.max(0, depth - 1);
9525
+ result.push(" ".repeat(depth) + line);
9526
+ continue;
9527
+ }
9528
+ if (_OPEN_ONLY_RE2.test(line)) {
9529
+ result.push(" ".repeat(depth) + line);
9530
+ depth++;
9531
+ continue;
9532
+ }
9533
+ result.push(" ".repeat(depth) + line);
9534
+ }
9535
+ return result.join("\n");
9536
+ }
9537
+
9538
+ // src/editor/table/TablePlugin.ts
9539
+ function getCellDOMElement(cellPathJson) {
9540
+ return document.querySelector(`[data-block-path='${cellPathJson}']`);
9541
+ }
9542
+ function isCaretAtContainerStart(container) {
9543
+ const sel = window.getSelection();
9544
+ if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
9545
+ const range = sel.getRangeAt(0);
9546
+ if (range.startOffset !== 0) return false;
9547
+ let node = range.startContainer;
9548
+ while (node && node !== container) {
9549
+ if (node.previousSibling) return false;
9550
+ node = node.parentNode;
9551
+ }
9552
+ return node === container;
9553
+ }
9554
+ function isCaretAtContainerEnd(container) {
9555
+ var _a, _b;
9556
+ const sel = window.getSelection();
9557
+ if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
9558
+ const range = sel.getRangeAt(0);
9559
+ const endNode = range.endContainer;
9560
+ const endOffset = range.endOffset;
9561
+ const len = endNode.nodeType === Node.TEXT_NODE ? (_b = (_a = endNode.textContent) == null ? void 0 : _a.length) != null ? _b : 0 : endNode.childNodes.length;
9562
+ if (endOffset !== len) return false;
9563
+ let node = endNode;
9564
+ while (node && node !== container) {
9565
+ if (node.nextSibling) return false;
9566
+ node = node.parentNode;
9567
+ }
9568
+ return node === container;
9569
+ }
9570
+ function isCaretOnEdgeLine(container, direction) {
9571
+ const sel = window.getSelection();
9572
+ if (!sel || sel.rangeCount === 0 || !sel.isCollapsed) return false;
9573
+ const caretRange = sel.getRangeAt(0).cloneRange();
9574
+ caretRange.collapse(true);
9575
+ const caretRects = caretRange.getClientRects();
9576
+ if (!caretRects.length) return true;
9577
+ const caretRect = caretRects[0];
9578
+ const containerRect = container.getBoundingClientRect();
9579
+ const lineHeight = parseFloat(getComputedStyle(container).lineHeight) || 20;
9580
+ if (direction === "up") {
9581
+ return caretRect.top < containerRect.top + lineHeight;
9582
+ } else {
9583
+ return caretRect.bottom > containerRect.bottom - lineHeight;
9584
+ }
9585
+ }
9586
+ var TablePlugin = {
9587
+ name: "table",
9588
+ keyBindings: {
9589
+ Tab: (engine) => {
9590
+ var _a;
9591
+ const state = engine.getState();
9592
+ const sel = state.selection;
9593
+ if (!sel) return false;
9594
+ const cellPos = findCellPosition(state.doc, sel.anchor.path);
9595
+ if (!cellPos) return false;
9596
+ const { tablePath, row, col } = cellPos;
9597
+ const table = getNodeAtPath(state.doc, tablePath);
9598
+ const { rows, cols } = getTableDimensions(table);
9599
+ let nextRow = row;
9600
+ let nextCol = col + 1;
9601
+ while (nextRow < rows) {
9602
+ if (nextCol >= cols) {
9603
+ nextCol = 0;
9604
+ nextRow++;
9605
+ continue;
9606
+ }
9607
+ const c = getNodeAtPath(state.doc, [...tablePath, nextRow, nextCol]);
9608
+ if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
9609
+ nextCol++;
9610
+ }
9611
+ if (nextRow >= rows) {
9612
+ const newTable = insertTableRowAfter(table, rows - 1);
9613
+ const newChildren = [...state.doc.children];
9614
+ newChildren[tablePath[0]] = newTable;
9615
+ const tempDoc = { type: "doc", children: newChildren };
9616
+ const tr2 = createTransaction();
9617
+ tr2.steps.push({ type: "delete_node", path: tablePath });
9618
+ tr2.steps.push({
9619
+ type: "insert_node",
9620
+ parentPath: tablePath.length > 1 ? tablePath.slice(0, -1) : [],
9621
+ index: tablePath[tablePath.length - 1],
9622
+ node: newTable
9623
+ });
9624
+ const pos2 = getCellFirstPosition(tempDoc, tablePath, rows, 0);
9625
+ if (pos2) tr2.steps.push(tr_setSelection(makeCollapsedSelection(pos2)));
9626
+ engine.dispatch(tr2);
9627
+ return true;
9628
+ }
9629
+ const pos = getCellFirstPosition(state.doc, tablePath, nextRow, nextCol);
9630
+ if (!pos) return false;
9631
+ const tr = createTransaction();
9632
+ tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
9633
+ engine.dispatch(tr);
9634
+ return true;
9635
+ },
9636
+ "ArrowRight": (engine) => {
9637
+ var _a;
9638
+ const state = engine.getState();
9639
+ const sel = state.selection;
9640
+ if (!sel) return false;
9641
+ const cellPos = findCellPosition(state.doc, sel.anchor.path);
9642
+ if (!cellPos) return false;
9643
+ const { tablePath, row, col } = cellPos;
9644
+ const cellPath = [...tablePath, row, col];
9645
+ const cellEl = getCellDOMElement(JSON.stringify(cellPath));
9646
+ if (!cellEl || !isCaretAtContainerEnd(cellEl)) return false;
9647
+ const table = getNodeAtPath(state.doc, tablePath);
9648
+ const { rows, cols } = getTableDimensions(table);
9649
+ let nextRow = row;
9650
+ let nextCol = col + 1;
9651
+ while (nextRow < rows) {
9652
+ if (nextCol >= cols) {
9653
+ nextCol = 0;
9654
+ nextRow++;
9655
+ continue;
9656
+ }
9657
+ const c = getNodeAtPath(state.doc, [...tablePath, nextRow, nextCol]);
9658
+ if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
9659
+ nextCol++;
9660
+ }
9661
+ if (nextRow >= rows) return true;
9662
+ const pos = getCellFirstPosition(state.doc, tablePath, nextRow, nextCol);
9663
+ if (!pos) return false;
9664
+ const tr = createTransaction();
9665
+ tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
9666
+ engine.dispatch(tr);
9667
+ return true;
9668
+ },
9669
+ "ArrowLeft": (engine) => {
9670
+ var _a;
9671
+ const state = engine.getState();
9672
+ const sel = state.selection;
9673
+ if (!sel) return false;
9674
+ const cellPos = findCellPosition(state.doc, sel.anchor.path);
9675
+ if (!cellPos) return false;
9676
+ const { tablePath, row, col } = cellPos;
9677
+ const cellPath = [...tablePath, row, col];
9678
+ const cellEl = getCellDOMElement(JSON.stringify(cellPath));
9679
+ if (!cellEl || !isCaretAtContainerStart(cellEl)) return false;
9680
+ const table = getNodeAtPath(state.doc, tablePath);
9681
+ const { cols } = getTableDimensions(table);
9682
+ let prevRow = row;
9683
+ let prevCol = col - 1;
9684
+ while (prevRow >= 0) {
9685
+ if (prevCol < 0) {
9686
+ prevCol = cols - 1;
9687
+ prevRow--;
9688
+ continue;
9689
+ }
9690
+ const c = getNodeAtPath(state.doc, [...tablePath, prevRow, prevCol]);
9691
+ if (!((_a = c == null ? void 0 : c.attrs) == null ? void 0 : _a.covered)) break;
9692
+ prevCol--;
9693
+ }
9694
+ if (prevRow < 0) return true;
9695
+ const pos = getCellLastPosition(state.doc, tablePath, prevRow, prevCol);
9696
+ if (!pos) return false;
9697
+ const tr = createTransaction();
9698
+ tr.steps.push(tr_setSelection(makeCollapsedSelection(pos)));
9699
+ engine.dispatch(tr);
9700
+ return true;
9701
+ },
8844
9702
  "ArrowDown": (engine) => {
8845
9703
  var _a;
8846
9704
  const state = engine.getState();
@@ -9139,151 +9997,6 @@ function execCommand(engine, command, ...args) {
9139
9997
  function registerCommand(name, fn) {
9140
9998
  REGISTRY[name] = fn;
9141
9999
  }
9142
- var Editor = forwardRef(function Editor2(props, ref) {
9143
- var _a;
9144
- const {
9145
- value,
9146
- defaultValue,
9147
- onChange,
9148
- placeholder,
9149
- readOnly = false,
9150
- toolbar = DEFAULT_TOOLBAR,
9151
- theme = "light",
9152
- plugins,
9153
- onFocus,
9154
- onBlur,
9155
- onReady,
9156
- onUploadImage,
9157
- className,
9158
- style,
9159
- minHeight,
9160
- maxHeight
9161
- } = props;
9162
- const engine = useEditorEngine();
9163
- const pluginsRegisteredRef = useRef(false);
9164
- if (!pluginsRegisteredRef.current && (plugins == null ? void 0 : plugins.length)) {
9165
- pluginsRegisteredRef.current = true;
9166
- for (const p of plugins) {
9167
- engine.registerPlugin(p);
9168
- }
9169
- }
9170
- const initializedRef = useRef(false);
9171
- useEffect(() => {
9172
- if (initializedRef.current) return;
9173
- initializedRef.current = true;
9174
- const initHTML = value != null ? value : defaultValue;
9175
- if (!initHTML) return;
9176
- const doc = htmlSerializer.deserialize(initHTML);
9177
- const tr = createTransaction();
9178
- tr.steps.push(tr_replaceDoc(doc));
9179
- engine.dispatch(tr);
9180
- }, []);
9181
- const lastEmittedRef = useRef("");
9182
- useEffect(() => {
9183
- if (value === void 0) return;
9184
- if (value === lastEmittedRef.current) return;
9185
- const currentHTML = htmlSerializer.serialize(engine.getState().doc);
9186
- if (value === currentHTML) return;
9187
- const doc = htmlSerializer.deserialize(value);
9188
- const tr = createTransaction();
9189
- tr.steps.push(tr_replaceDoc(doc));
9190
- engine.dispatch(tr);
9191
- }, [value, engine]);
9192
- const handleHTMLChange = useCallback((html) => {
9193
- lastEmittedRef.current = html;
9194
- onChange == null ? void 0 : onChange(html);
9195
- }, [onChange]);
9196
- const rootRef = useRef(null);
9197
- const api = useMemo(() => ({
9198
- getHTML: () => htmlSerializer.serialize(engine.getState().doc),
9199
- setHTML: (html) => {
9200
- const doc = htmlSerializer.deserialize(html);
9201
- const tr = createTransaction();
9202
- tr.steps.push(tr_replaceDoc(doc));
9203
- engine.dispatch(tr);
9204
- },
9205
- getJSON: () => engine.getState().doc,
9206
- getMarkdown: () => markdownSerializer.serialize(engine.getState().doc),
9207
- focus: () => {
9208
- var _a2;
9209
- const ce = (_a2 = rootRef.current) == null ? void 0 : _a2.querySelector("[contenteditable]");
9210
- ce == null ? void 0 : ce.focus();
9211
- },
9212
- blur: () => {
9213
- var _a2;
9214
- const ce = (_a2 = rootRef.current) == null ? void 0 : _a2.querySelector("[contenteditable]");
9215
- ce == null ? void 0 : ce.blur();
9216
- },
9217
- clear: () => {
9218
- const tr = createTransaction();
9219
- tr.steps.push(tr_replaceDoc(createEmptyDocument()));
9220
- engine.dispatch(tr);
9221
- },
9222
- undo: () => {
9223
- engine.handleKeyDown(
9224
- new KeyboardEvent("keydown", { key: "z", ctrlKey: true, bubbles: true })
9225
- );
9226
- },
9227
- redo: () => {
9228
- engine.handleKeyDown(
9229
- new KeyboardEvent("keydown", { key: "y", ctrlKey: true, bubbles: true })
9230
- );
9231
- },
9232
- execCommand: (command, ...args) => {
9233
- return execCommand(engine, command, ...args);
9234
- },
9235
- registerCommand: (name, fn) => {
9236
- registerCommand(name, fn);
9237
- },
9238
- getEngine: () => engine
9239
- }), [engine]);
9240
- useImperativeHandle(ref, () => api, [api]);
9241
- useEffect(() => {
9242
- onReady == null ? void 0 : onReady(api);
9243
- }, []);
9244
- const themeMode = typeof theme === "string" ? theme : (_a = theme.mode) != null ? _a : "light";
9245
- const themeTokenOverrides = typeof theme === "object" && theme.tokens ? theme.tokens : void 0;
9246
- const rootStyle = useMemo(() => {
9247
- const base = { ...style };
9248
- if (minHeight !== void 0)
9249
- base.minHeight = typeof minHeight === "number" ? `${minHeight}px` : minHeight;
9250
- if (maxHeight !== void 0) {
9251
- base.maxHeight = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
9252
- base.overflow = "auto";
9253
- }
9254
- if (themeTokenOverrides) {
9255
- for (const [key, val] of Object.entries(themeTokenOverrides)) {
9256
- const cssVar = `--editor-${key.replace(/([A-Z])/g, (m) => `-${m.toLowerCase()}`)}`;
9257
- base[cssVar] = val;
9258
- }
9259
- }
9260
- return base;
9261
- }, [style, minHeight, maxHeight, themeTokenOverrides]);
9262
- return /* @__PURE__ */ jsx(
9263
- "div",
9264
- {
9265
- ref: rootRef,
9266
- "data-editor-theme": themeMode,
9267
- "data-traffica-editor": "",
9268
- className,
9269
- style: rootStyle,
9270
- children: /* @__PURE__ */ jsx(
9271
- EditorCore,
9272
- {
9273
- engine,
9274
- placeholder,
9275
- readOnly,
9276
- hideToolbar: toolbar === false,
9277
- toolbarConfig: toolbar !== false ? toolbar : void 0,
9278
- onHTMLChange: handleHTMLChange,
9279
- onFocus,
9280
- onBlur,
9281
- onUploadImage
9282
- }
9283
- )
9284
- }
9285
- );
9286
- });
9287
10000
 
9288
10001
  export { BASIC_TOOLBAR, DEFAULT_TOOLBAR, Editor, EditorCore, EditorEngine, MINIMAL_TOOLBAR, TablePlugin, Toolbar, addColumnLeft, addColumnRight, addMarkToNode, addRowAbove, addRowBelow, applyMarkToRange, applySearchHighlights, applyTransaction, attachClipboardHandlers, attachPasteHandler, captureSelection, clearSearchHighlights, collectText, comparePaths, comparePositions, createEmptyDocument, createHistoryManager, createHistoryPlugin, createParagraph, createPastePlugin, createTableCell, createTableNode, createTransaction, deleteColumn, deleteImageAtPath, deleteRange, deleteRow, deleteTable, deleteTableColumn, deleteTableRow, deleteTextAtPath, execCommand, findCellPosition, findContentBlockPath, findMatches, findTablePath, getActiveAlignment, getActiveBlockType, getActiveFontFamily, getActiveFontSize, getActiveHighlightColor, getActiveLinkHref, getActiveLinkRange, getActiveMarks, getActiveTextColor, getCellFirstPosition, getCellLastPosition, getDocumentLength, getDocumentMarkAttrValues, getNodeAtPath, getTableDimensions, getTextNodesBetween, htmlSerializer, insertImage, insertLink, insertTable, insertTableColumnAfter, insertTableColumnBefore, insertTableRowAfter, insertTableRowBefore, insertText, insertTextAtPath, isBlockNode, isContainerBlock, isSelectionInContainer, isTextNode, joinBlocks, jsonSerializer, makeCollapsedSelection, makePosition, markdownSerializer, marksEqual, mergeAdjacentTextNodesInDoc, mergeCells, mergeTableCells, normalizeRange, redo, registerCommand, removeMarkFromNode, removeMarkFromRange, renderDocument, replaceAllMatches, replaceMatch, restoreSelection, scrollMatchIntoView, setAlignment, setBlockType2 as setBlockType, setCodeBlockLanguage, setColumnWidth, setFontFamily, setFontSize, setHighlightColor, setImageAttr, setMarkOnRange, setTextColor, splitBlock, splitCell, splitTableCell, toggleCheckItemAt, toggleHeaderRow, toggleMark, undo, updateColumnWidth, useEditorEngine, useEditorState, walkDocument };
9289
10002
  //# sourceMappingURL=index.mjs.map