@underverse-ui/underverse 1.0.185 → 1.0.187

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.js CHANGED
@@ -815,7 +815,8 @@ var en_default = {
815
815
  addBlock: "Add block"
816
816
  },
817
817
  linkInput: {
818
- placeholder: "Paste or type a link..."
818
+ placeholder: "Paste or type a link...",
819
+ invalid: "Enter a valid link, such as https://example.com."
819
820
  },
820
821
  imageInput: {
821
822
  urlTab: "URL",
@@ -1242,7 +1243,8 @@ var vi_default = {
1242
1243
  addBlock: "Th\xEAm kh\u1ED1i"
1243
1244
  },
1244
1245
  linkInput: {
1245
- placeholder: "D\xE1n ho\u1EB7c nh\u1EADp li\xEAn k\u1EBFt..."
1246
+ placeholder: "D\xE1n ho\u1EB7c nh\u1EADp li\xEAn k\u1EBFt...",
1247
+ invalid: "Vui l\xF2ng nh\u1EADp li\xEAn k\u1EBFt h\u1EE3p l\u1EC7, v\xED d\u1EE5: https://example.com."
1246
1248
  },
1247
1249
  imageInput: {
1248
1250
  urlTab: "URL",
@@ -1669,7 +1671,8 @@ var ko_default = {
1669
1671
  addBlock: "\uBE14\uB85D \uCD94\uAC00"
1670
1672
  },
1671
1673
  linkInput: {
1672
- placeholder: "\uB9C1\uD06C \uBD99\uC5EC\uB123\uAE30 \uB610\uB294 \uC785\uB825..."
1674
+ placeholder: "\uB9C1\uD06C \uBD99\uC5EC\uB123\uAE30 \uB610\uB294 \uC785\uB825...",
1675
+ invalid: "https://example.com\uACFC \uAC19\uC740 \uC62C\uBC14\uB978 \uB9C1\uD06C\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694."
1673
1676
  },
1674
1677
  imageInput: {
1675
1678
  urlTab: "URL",
@@ -2096,7 +2099,8 @@ var ja_default = {
2096
2099
  addBlock: "\u30D6\u30ED\u30C3\u30AF\u3092\u8FFD\u52A0"
2097
2100
  },
2098
2101
  linkInput: {
2099
- placeholder: "\u30EA\u30F3\u30AF\u3092\u8CBC\u308A\u4ED8\u3051\u307E\u305F\u306F\u5165\u529B..."
2102
+ placeholder: "\u30EA\u30F3\u30AF\u3092\u8CBC\u308A\u4ED8\u3051\u307E\u305F\u306F\u5165\u529B...",
2103
+ invalid: "https://example.com \u306E\u3088\u3046\u306A\u6709\u52B9\u306A\u30EA\u30F3\u30AF\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
2100
2104
  },
2101
2105
  imageInput: {
2102
2106
  urlTab: "URL",
@@ -27566,6 +27570,32 @@ function isProtocolRelativeUrl(value) {
27566
27570
  function isRelativeUrl(value) {
27567
27571
  return value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value.startsWith("#");
27568
27572
  }
27573
+ function isValidIpv4Hostname(hostname) {
27574
+ const parts = hostname.split(".");
27575
+ return parts.length === 4 && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
27576
+ }
27577
+ function isValidWebHostname(hostname) {
27578
+ const normalized = hostname.toLowerCase();
27579
+ if (normalized === "localhost" || isValidIpv4Hostname(normalized)) return true;
27580
+ if (normalized.startsWith("[") && normalized.endsWith("]") && normalized.includes(":")) return true;
27581
+ const labels = normalized.split(".");
27582
+ if (labels.length < 2) return false;
27583
+ const validLabel = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)$/;
27584
+ return labels.every((label) => validLabel.test(label)) && /[a-z]/.test(labels.at(-1) ?? "");
27585
+ }
27586
+ function isValidLinkUrl(parsed) {
27587
+ if (parsed.protocol === "http:" || parsed.protocol === "https:") {
27588
+ return isValidWebHostname(parsed.hostname);
27589
+ }
27590
+ if (parsed.protocol === "mailto:") {
27591
+ return /^[^@]+@[^@]+\.[^@]+$/.test(decodeURIComponent(parsed.pathname));
27592
+ }
27593
+ if (parsed.protocol === "tel:") {
27594
+ const number = decodeURIComponent(parsed.pathname);
27595
+ return /^\+?[\d().-]+$/.test(number) && (number.match(/\d/g)?.length ?? 0) >= 3;
27596
+ }
27597
+ return false;
27598
+ }
27569
27599
  function isDataImageUrl(value) {
27570
27600
  return /^data:image\/(?:png|jpe?g|gif|webp|svg\+xml|bmp|x-icon|avif);base64,/i.test(value);
27571
27601
  }
@@ -27583,7 +27613,7 @@ function isSafeUEditorUrl(raw, kind) {
27583
27613
  const parsed = new URL(value);
27584
27614
  if (kind === "image") return IMAGE_PROTOCOLS.has(parsed.protocol);
27585
27615
  if (kind === "file") return FILE_PROTOCOLS.has(parsed.protocol);
27586
- return LINK_PROTOCOLS.has(parsed.protocol);
27616
+ return LINK_PROTOCOLS.has(parsed.protocol) && isValidLinkUrl(parsed);
27587
27617
  } catch {
27588
27618
  return false;
27589
27619
  }
@@ -32379,7 +32409,7 @@ function deleteSelectedImage(editor) {
32379
32409
  }
32380
32410
 
32381
32411
  // src/components/UEditor/inputs.tsx
32382
- import { useEffect as useEffect36, useRef as useRef32, useState as useState48 } from "react";
32412
+ import { useEffect as useEffect36, useId as useId15, useRef as useRef32, useState as useState48 } from "react";
32383
32413
  import { Check as Check13, X as X19 } from "lucide-react";
32384
32414
  import { jsx as jsx90, jsxs as jsxs74 } from "react/jsx-runtime";
32385
32415
  function normalizeUrl(raw) {
@@ -32392,7 +32422,9 @@ var LinkInput = ({
32392
32422
  }) => {
32393
32423
  const t = useSmartTranslations("UEditor");
32394
32424
  const [url, setUrl] = useState48(initialUrl);
32425
+ const [error, setError] = useState48("");
32395
32426
  const inputRef = useRef32(null);
32427
+ const errorId = useId15();
32396
32428
  useEffect36(() => {
32397
32429
  inputRef.current?.focus();
32398
32430
  inputRef.current?.select();
@@ -32400,22 +32432,35 @@ var LinkInput = ({
32400
32432
  const handleSubmit = (e) => {
32401
32433
  e.preventDefault();
32402
32434
  const normalized = normalizeUrl(url);
32403
- if (normalized) onSubmit(normalized);
32435
+ if (!normalized) {
32436
+ setError(t("linkInput.invalid"));
32437
+ return;
32438
+ }
32439
+ setError("");
32440
+ onSubmit(normalized);
32404
32441
  };
32405
- return /* @__PURE__ */ jsxs74("form", { onSubmit: handleSubmit, className: "flex items-center gap-2 p-2", children: [
32406
- /* @__PURE__ */ jsx90(
32407
- "input",
32408
- {
32409
- ref: inputRef,
32410
- type: "text",
32411
- value: url,
32412
- onChange: (e) => setUrl(e.target.value),
32413
- placeholder: t("linkInput.placeholder"),
32414
- className: "flex-1 px-3 py-2 text-sm bg-muted/50 border-0 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/20"
32415
- }
32416
- ),
32417
- /* @__PURE__ */ jsx90("button", { type: "submit", className: "p-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors", children: /* @__PURE__ */ jsx90(Check13, { className: "w-4 h-4" }) }),
32418
- /* @__PURE__ */ jsx90("button", { type: "button", onClick: onCancel, className: "p-2 rounded-lg hover:bg-muted transition-colors text-muted-foreground", children: /* @__PURE__ */ jsx90(X19, { className: "w-4 h-4" }) })
32442
+ return /* @__PURE__ */ jsxs74("form", { onSubmit: handleSubmit, className: "p-2", children: [
32443
+ /* @__PURE__ */ jsxs74("div", { className: "flex items-center gap-2", children: [
32444
+ /* @__PURE__ */ jsx90(
32445
+ "input",
32446
+ {
32447
+ ref: inputRef,
32448
+ type: "text",
32449
+ value: url,
32450
+ onChange: (e) => {
32451
+ setUrl(e.target.value);
32452
+ if (error) setError("");
32453
+ },
32454
+ placeholder: t("linkInput.placeholder"),
32455
+ "aria-invalid": Boolean(error),
32456
+ "aria-describedby": error ? errorId : void 0,
32457
+ className: "flex-1 px-3 py-2 text-sm bg-muted/50 border-0 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/20 aria-invalid:ring-2 aria-invalid:ring-destructive/40"
32458
+ }
32459
+ ),
32460
+ /* @__PURE__ */ jsx90("button", { type: "submit", className: "p-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 transition-colors", children: /* @__PURE__ */ jsx90(Check13, { className: "w-4 h-4" }) }),
32461
+ /* @__PURE__ */ jsx90("button", { type: "button", onClick: onCancel, className: "p-2 rounded-lg hover:bg-muted transition-colors text-muted-foreground", children: /* @__PURE__ */ jsx90(X19, { className: "w-4 h-4" }) })
32462
+ ] }),
32463
+ error ? /* @__PURE__ */ jsx90("p", { id: errorId, role: "alert", className: "mt-1.5 px-1 text-xs text-destructive", children: error }) : null
32419
32464
  ] });
32420
32465
  };
32421
32466
  var ImageInput = ({ onSubmit, onCancel }) => {
@@ -32476,8 +32521,28 @@ var ImageInput = ({ onSubmit, onCancel }) => {
32476
32521
  ] });
32477
32522
  };
32478
32523
 
32479
- // src/components/UEditor/table-cell-commands.ts
32524
+ // src/components/UEditor/link-commands.ts
32480
32525
  import { TextSelection as TextSelection2 } from "@tiptap/pm/state";
32526
+ function applyEditorLink(editor, href) {
32527
+ const isEditingLink = editor.isActive("link");
32528
+ const hasSelectedText = !editor.state.selection.empty;
32529
+ const chain = editor.chain().focus();
32530
+ if (isEditingLink) {
32531
+ chain.extendMarkRange("link");
32532
+ }
32533
+ chain.setLink({ href });
32534
+ if (!hasSelectedText && !isEditingLink) {
32535
+ chain.insertContent(href);
32536
+ }
32537
+ return chain.command(({ tr }) => {
32538
+ tr.setSelection(TextSelection2.create(tr.doc, tr.selection.to));
32539
+ tr.removeStoredMark(editor.schema.marks.link);
32540
+ return true;
32541
+ }).run();
32542
+ }
32543
+
32544
+ // src/components/UEditor/table-cell-commands.ts
32545
+ import { TextSelection as TextSelection3 } from "@tiptap/pm/state";
32481
32546
  import { selectedRect, TableMap as TableMap2 } from "@tiptap/pm/tables";
32482
32547
  function getCellSelectionPositions(selection) {
32483
32548
  const value = selection;
@@ -32511,7 +32576,7 @@ function getFocusableCellPos(editor, cellPos) {
32511
32576
  return node?.isTextblock ? offset + 1 : cellPos + 1;
32512
32577
  }
32513
32578
  function focusCell(editor, cellPos) {
32514
- const selection = TextSelection2.near(editor.state.doc.resolve(getFocusableCellPos(editor, cellPos)));
32579
+ const selection = TextSelection3.near(editor.state.doc.resolve(getFocusableCellPos(editor, cellPos)));
32515
32580
  editor.view.dispatch(editor.state.tr.setSelection(selection));
32516
32581
  editor.view.focus();
32517
32582
  }
@@ -33057,7 +33122,7 @@ var EditorToolbar = ({
33057
33122
  {
33058
33123
  initialUrl: String(editor.getAttributes("link").href ?? ""),
33059
33124
  onSubmit: (url) => {
33060
- editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
33125
+ applyEditorLink(editor, url);
33061
33126
  setShowLinkInput(false);
33062
33127
  },
33063
33128
  onCancel: () => setShowLinkInput(false)
@@ -33294,7 +33359,7 @@ var EditorToolbar = ({
33294
33359
  {
33295
33360
  initialUrl: String(editor.getAttributes("link").href ?? ""),
33296
33361
  onSubmit: (url) => {
33297
- editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
33362
+ applyEditorLink(editor, url);
33298
33363
  setShowLinkInput(false);
33299
33364
  },
33300
33365
  onCancel: () => setShowLinkInput(false)
@@ -33916,7 +33981,7 @@ import {
33916
33981
  } from "lucide-react";
33917
33982
 
33918
33983
  // src/components/UEditor/table-formula-commands.ts
33919
- import { TextSelection as TextSelection3 } from "@tiptap/pm/state";
33984
+ import { TextSelection as TextSelection4 } from "@tiptap/pm/state";
33920
33985
  import { selectedRect as selectedRect2, setCellAttr as setCellAttr2, TableMap as TableMap3 } from "@tiptap/pm/tables";
33921
33986
 
33922
33987
  // src/components/UEditor/table-formula.ts
@@ -34532,7 +34597,7 @@ function restoreSelectionAtTableCell(editor, anchor) {
34532
34597
  if (!cellNode || cellNode.type.name !== "tableCell" && cellNode.type.name !== "tableHeader") return false;
34533
34598
  const selectionPos = Math.min(cellPos + 2, editor.state.doc.content.size);
34534
34599
  editor.view.dispatch(
34535
- editor.state.tr.setSelection(TextSelection3.near(editor.state.doc.resolve(selectionPos))).setMeta("addToHistory", false)
34600
+ editor.state.tr.setSelection(TextSelection4.near(editor.state.doc.resolve(selectionPos))).setMeta("addToHistory", false)
34536
34601
  );
34537
34602
  return true;
34538
34603
  }
@@ -34609,7 +34674,7 @@ function removeSelectedTableCellFormula(editor, clearContent) {
34609
34674
  );
34610
34675
  let transaction = editor.state.tr.replaceWith(selectedCell.cellPos, selectedCell.cellPos + selectedCell.node.nodeSize, nextCell2).setMeta(UEDITOR_TABLE_FORMULA_RECALCULATE_META, true);
34611
34676
  const selectionPos = Math.min(selectedCell.cellPos + 2, transaction.doc.content.size);
34612
- transaction = transaction.setSelection(TextSelection3.near(transaction.doc.resolve(selectionPos)));
34677
+ transaction = transaction.setSelection(TextSelection4.near(transaction.doc.resolve(selectionPos)));
34613
34678
  editor.view.dispatch(transaction);
34614
34679
  recalculateActiveTableFormulas(editor);
34615
34680
  restoreSelectionAtTableCell(editor, selectedCell);
@@ -35095,7 +35160,7 @@ var BubbleMenuContent = ({
35095
35160
  {
35096
35161
  initialUrl: editor.getAttributes("link").href || "",
35097
35162
  onSubmit: (url) => {
35098
- editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
35163
+ applyEditorLink(editor, url);
35099
35164
  setShowLinkInput(false);
35100
35165
  requestAnimationFrame(() => editor.commands.focus());
35101
35166
  },
@@ -38952,7 +39017,7 @@ var Selection = class {
38952
39017
  found.
38953
39018
  */
38954
39019
  static findFrom($pos, dir, textOnly = false) {
38955
- let inner = $pos.parent.inlineContent ? new TextSelection4($pos) : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly);
39020
+ let inner = $pos.parent.inlineContent ? new TextSelection5($pos) : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly);
38956
39021
  if (inner)
38957
39022
  return inner;
38958
39023
  for (let depth = $pos.depth - 1; depth >= 0; depth--) {
@@ -39021,7 +39086,7 @@ var Selection = class {
39021
39086
  returns the bookmark for that.
39022
39087
  */
39023
39088
  getBookmark() {
39024
- return TextSelection4.between(this.$anchor, this.$head).getBookmark();
39089
+ return TextSelection5.between(this.$anchor, this.$head).getBookmark();
39025
39090
  }
39026
39091
  };
39027
39092
  Selection.prototype.visible = true;
@@ -39041,7 +39106,7 @@ function checkTextSelection($pos) {
39041
39106
  console["warn"]("TextSelection endpoint not pointing into a node with inline content (" + $pos.parent.type.name + ")");
39042
39107
  }
39043
39108
  }
39044
- var TextSelection4 = class _TextSelection extends Selection {
39109
+ var TextSelection5 = class _TextSelection extends Selection {
39045
39110
  /**
39046
39111
  Construct a text selection between the given points.
39047
39112
  */
@@ -39127,7 +39192,7 @@ var TextSelection4 = class _TextSelection extends Selection {
39127
39192
  return new _TextSelection($anchor, $head);
39128
39193
  }
39129
39194
  };
39130
- Selection.jsonID("text", TextSelection4);
39195
+ Selection.jsonID("text", TextSelection5);
39131
39196
  var TextBookmark = class _TextBookmark {
39132
39197
  constructor(anchor, head) {
39133
39198
  this.anchor = anchor;
@@ -39137,7 +39202,7 @@ var TextBookmark = class _TextBookmark {
39137
39202
  return new _TextBookmark(mapping.map(this.anchor), mapping.map(this.head));
39138
39203
  }
39139
39204
  resolve(doc) {
39140
- return TextSelection4.between(doc.resolve(this.anchor), doc.resolve(this.head));
39205
+ return TextSelection5.between(doc.resolve(this.anchor), doc.resolve(this.head));
39141
39206
  }
39142
39207
  };
39143
39208
  var NodeSelection2 = class _NodeSelection extends Selection {
@@ -39256,7 +39321,7 @@ var AllBookmark = {
39256
39321
  };
39257
39322
  function findSelectionIn(doc, node, pos, index, dir, text = false) {
39258
39323
  if (node.inlineContent)
39259
- return TextSelection4.create(doc, pos);
39324
+ return TextSelection5.create(doc, pos);
39260
39325
  for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) {
39261
39326
  let child = node.child(i);
39262
39327
  if (!child.isAtom) {
@@ -39848,7 +39913,7 @@ var CellSelection = class CellSelection2 extends Selection {
39848
39913
  else if (tableChanged && this.isColSelection()) return CellSelection2.colSelection($anchorCell, $headCell);
39849
39914
  else return new CellSelection2($anchorCell, $headCell);
39850
39915
  }
39851
- return TextSelection4.between($anchorCell, $headCell);
39916
+ return TextSelection5.between($anchorCell, $headCell);
39852
39917
  }
39853
39918
  content() {
39854
39919
  const table = this.$anchorCell.node(-1);
@@ -40463,7 +40528,7 @@ function shiftArrow(axis, dir) {
40463
40528
  };
40464
40529
  }
40465
40530
  function atEndOfCell(view, axis, dir) {
40466
- if (!(view.state.selection instanceof TextSelection4)) return null;
40531
+ if (!(view.state.selection instanceof TextSelection5)) return null;
40467
40532
  const { $head } = view.state.selection;
40468
40533
  for (let d = $head.depth - 1; d >= 0; d--) {
40469
40534
  const parent = $head.node(d);
@@ -42365,7 +42430,7 @@ import { useEffect as useEffect39, useRef as useRef37 } from "react";
42365
42430
  import { TableMap as TableMap6 } from "@tiptap/pm/tables";
42366
42431
 
42367
42432
  // src/components/UEditor/table-formula-range-picker.ts
42368
- import { TextSelection as TextSelection5 } from "@tiptap/pm/state";
42433
+ import { TextSelection as TextSelection6 } from "@tiptap/pm/state";
42369
42434
  import { TableMap as TableMap5 } from "@tiptap/pm/tables";
42370
42435
  function getCellText2(cellNode) {
42371
42436
  return cellNode.textBetween(0, cellNode.content.size, "\n").trim();
@@ -42384,6 +42449,9 @@ function getFormulaEditingTableContext(view) {
42384
42449
  if (!tableNode || tableNode.type.name !== "table") return null;
42385
42450
  const tableDom = cellDom.closest("table");
42386
42451
  if (!(tableDom instanceof HTMLTableElement)) return null;
42452
+ const tableMap = TableMap5.get(tableNode);
42453
+ const tableStart = $from.start(tableDepth);
42454
+ const formulaCellRect = tableMap.findCell(cellPos - tableStart);
42387
42455
  return {
42388
42456
  cellContentEnd: $from.end(depth),
42389
42457
  cellContentStart: $from.start(depth),
@@ -42391,17 +42459,56 @@ function getFormulaEditingTableContext(view) {
42391
42459
  formula: getCellText2(node),
42392
42460
  tableDom,
42393
42461
  tableNode,
42394
- tablePos: $from.before(tableDepth)
42462
+ tablePos: $from.before(tableDepth),
42463
+ formulaCellLabel: `${indexToColumnName(formulaCellRect.left)}${formulaCellRect.top + 1}`
42395
42464
  };
42396
42465
  }
42397
42466
  return null;
42398
42467
  }
42468
+ function collectTableFormulaCells(tableNode) {
42469
+ const tableMap = TableMap5.get(tableNode);
42470
+ const formulas = [];
42471
+ tableNode.forEach((rowNode, rowOffset) => {
42472
+ rowNode.forEach((cellNode, cellOffset) => {
42473
+ const formula = typeof cellNode.attrs.formula === "string" ? cellNode.attrs.formula.trim() : "";
42474
+ if (!formula) return;
42475
+ const rect = tableMap.findCell(rowOffset + 1 + cellOffset);
42476
+ formulas.push({
42477
+ label: `${indexToColumnName(rect.left)}${rect.top + 1}`,
42478
+ formula
42479
+ });
42480
+ });
42481
+ });
42482
+ return formulas;
42483
+ }
42484
+ function getFormulaCycleBlockedLabels(tableNode, formulaCellLabel) {
42485
+ const graph = buildTableFormulaDependencyGraph(collectTableFormulaCells(tableNode));
42486
+ const blocked = /* @__PURE__ */ new Set();
42487
+ const queue = [formulaCellLabel.toUpperCase()];
42488
+ for (let index = 0; index < queue.length; index += 1) {
42489
+ const label = queue[index];
42490
+ if (!label || blocked.has(label)) continue;
42491
+ blocked.add(label);
42492
+ for (const dependent of graph.dependents.get(label) ?? []) {
42493
+ if (!blocked.has(dependent)) queue.push(dependent);
42494
+ }
42495
+ }
42496
+ return blocked;
42497
+ }
42498
+ function getReferenceLabels(reference) {
42499
+ const range = parseTableCellRange(reference);
42500
+ return range ? getTableCellRangeLabels(range) : [reference.toUpperCase()];
42501
+ }
42502
+ function isBlockedFormulaReference(tableNode, formulaCellLabel, reference) {
42503
+ const blockedLabels = getFormulaCycleBlockedLabels(tableNode, formulaCellLabel);
42504
+ return getReferenceLabels(reference).some((label) => blockedLabels.has(label));
42505
+ }
42399
42506
  function cancelFormulaEditing(view) {
42400
42507
  const context = getFormulaEditingTableContext(view);
42401
42508
  if (!context) return false;
42402
42509
  let tr = view.state.tr.delete(context.cellContentStart, context.cellContentEnd);
42403
42510
  const selectionPos = Math.min(context.cellContentStart, tr.doc.content.size);
42404
- tr = tr.setSelection(TextSelection5.near(tr.doc.resolve(selectionPos)));
42511
+ tr = tr.setSelection(TextSelection6.near(tr.doc.resolve(selectionPos)));
42405
42512
  view.dispatch(tr);
42406
42513
  view.focus();
42407
42514
  return true;
@@ -42459,19 +42566,30 @@ function normalizeFormulaRangeLabel(fromLabel, toLabel) {
42459
42566
  return fromLabel === toLabel ? fromLabel : `${fromLabel}:${toLabel}`;
42460
42567
  }
42461
42568
  function replacePickedLabel(view, pickState, nextLabel, currentCell) {
42462
- if (nextLabel === pickState.currentLabel && currentCell === pickState.currentCell) return pickState;
42569
+ const formulaContext = getFormulaEditingTableContext(view);
42570
+ const blocked = formulaContext ? isBlockedFormulaReference(formulaContext.tableNode, pickState.formulaCellLabel, nextLabel) : false;
42571
+ if (blocked) {
42572
+ return {
42573
+ ...pickState,
42574
+ blocked: true,
42575
+ currentCell
42576
+ };
42577
+ }
42578
+ if (nextLabel === pickState.currentLabel && currentCell === pickState.currentCell && !pickState.blocked) return pickState;
42463
42579
  if (nextLabel === pickState.currentLabel) {
42464
42580
  return {
42465
42581
  ...pickState,
42582
+ blocked: false,
42466
42583
  currentCell
42467
42584
  };
42468
42585
  }
42469
42586
  let tr = view.state.tr.insertText(nextLabel, pickState.insertedFrom, pickState.insertedTo);
42470
42587
  const nextTo = pickState.insertedFrom + nextLabel.length;
42471
- tr = tr.setSelection(TextSelection5.create(tr.doc, nextTo));
42588
+ tr = tr.setSelection(TextSelection6.create(tr.doc, nextTo));
42472
42589
  view.dispatch(tr);
42473
42590
  return {
42474
42591
  ...pickState,
42592
+ blocked: false,
42475
42593
  insertedTo: nextTo,
42476
42594
  currentLabel: nextLabel,
42477
42595
  currentCell
@@ -42483,12 +42601,28 @@ function beginFormulaRangePick(view, event) {
42483
42601
  if (!formulaCell) return null;
42484
42602
  const picked = getFormulaTableCellInfo(view, event.target, formulaCell.tablePos);
42485
42603
  if (!picked || picked.cell === formulaCell.cellDom) return null;
42604
+ const blocked = isBlockedFormulaReference(formulaCell.tableNode, formulaCell.formulaCellLabel, picked.label);
42605
+ if (blocked) {
42606
+ event.preventDefault();
42607
+ event.stopPropagation();
42608
+ return {
42609
+ anchorLabel: picked.label,
42610
+ anchorCell: picked.cell,
42611
+ tablePos: formulaCell.tablePos,
42612
+ insertedFrom: view.state.selection.from,
42613
+ insertedTo: view.state.selection.from,
42614
+ currentLabel: "",
42615
+ currentCell: picked.cell,
42616
+ blocked: true,
42617
+ formulaCellLabel: formulaCell.formulaCellLabel
42618
+ };
42619
+ }
42486
42620
  const { from, to } = view.state.selection;
42487
42621
  const prefix = from === to ? getReferenceInsertionPrefix(view, formulaCell.cellContentStart, from) : "";
42488
42622
  const insertedText = `${prefix}${picked.label}`;
42489
42623
  const insertedFrom = from + prefix.length;
42490
42624
  let tr = view.state.tr.insertText(insertedText, from, to);
42491
- tr = tr.setSelection(TextSelection5.create(tr.doc, from + insertedText.length));
42625
+ tr = tr.setSelection(TextSelection6.create(tr.doc, from + insertedText.length));
42492
42626
  view.dispatch(tr);
42493
42627
  view.focus();
42494
42628
  event.preventDefault();
@@ -42500,7 +42634,9 @@ function beginFormulaRangePick(view, event) {
42500
42634
  insertedFrom,
42501
42635
  insertedTo: insertedFrom + picked.label.length,
42502
42636
  currentLabel: picked.label,
42503
- currentCell: picked.cell
42637
+ currentCell: picked.cell,
42638
+ blocked: false,
42639
+ formulaCellLabel: formulaCell.formulaCellLabel
42504
42640
  };
42505
42641
  }
42506
42642
  function updateFormulaRangePick(view, pickState, event) {
@@ -42528,7 +42664,8 @@ function getFormulaRangePickHighlight(container, pickState) {
42528
42664
  left,
42529
42665
  top,
42530
42666
  width: Math.max(0, right - left),
42531
- height: Math.max(0, bottom - top)
42667
+ height: Math.max(0, bottom - top),
42668
+ blocked: pickState.blocked
42532
42669
  };
42533
42670
  }
42534
42671
 
@@ -42552,6 +42689,12 @@ function createReferenceHighlight(label) {
42552
42689
  element.className = "pointer-events-none absolute z-[21] rounded-[2px] border-2 border-emerald-500 bg-emerald-500/15";
42553
42690
  return element;
42554
42691
  }
42692
+ function createBlockedReferenceHighlight(label) {
42693
+ const element = document.createElement("span");
42694
+ element.dataset.ueditorFormulaBlockedReference = label;
42695
+ element.className = "pointer-events-none absolute z-[22] rounded-[2px] border-2 border-destructive/80 bg-destructive/10";
42696
+ return element;
42697
+ }
42555
42698
  function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42556
42699
  const overlayRef = useRef37(null);
42557
42700
  useEffect39(() => {
@@ -42563,6 +42706,7 @@ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42563
42706
  let activeTable = null;
42564
42707
  let activeTablePos = null;
42565
42708
  let hoverLabel = null;
42709
+ const scrollListenerOptions = { passive: true, capture: true };
42566
42710
  const clearOverlay = () => {
42567
42711
  activeTable = null;
42568
42712
  activeTablePos = null;
@@ -42622,10 +42766,12 @@ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42622
42766
  actions.dataset.ueditorFormulaActions = "";
42623
42767
  actions.className = "pointer-events-auto absolute z-50 flex items-center gap-1 rounded-md border border-border bg-background p-1 shadow-md";
42624
42768
  const formulaCellRect = context.cellDom.getBoundingClientRect();
42769
+ const formulaActionsHeight = 34;
42770
+ const formulaActionsTop = formulaCellRect.bottom + 4 + formulaActionsHeight > tableRect.bottom ? Math.max(containerRect.top + 4, formulaCellRect.top - formulaActionsHeight - 4) : formulaCellRect.bottom + 4;
42625
42771
  setPosition(
42626
42772
  actions,
42627
42773
  formulaCellRect.left - containerRect.left + container.scrollLeft,
42628
- formulaCellRect.bottom - containerRect.top + container.scrollTop + 4
42774
+ formulaActionsTop - containerRect.top + container.scrollTop
42629
42775
  );
42630
42776
  const applyButton = document.createElement("button");
42631
42777
  applyButton.type = "button";
@@ -42677,10 +42823,12 @@ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42677
42823
  children.push(label);
42678
42824
  }
42679
42825
  const references = new Set(getTableFormulaReferences(context.formula));
42826
+ const blockedReferences = getFormulaCycleBlockedLabels(context.tableNode, context.formulaCellLabel);
42680
42827
  for (const info of cellInfos) {
42681
- if (!info || !references.has(info.label)) continue;
42828
+ if (!info) continue;
42682
42829
  const cellRect = info.cell.getBoundingClientRect();
42683
- const highlight = createReferenceHighlight(info.label);
42830
+ const highlight = info.label !== context.formulaCellLabel && blockedReferences.has(info.label) ? createBlockedReferenceHighlight(info.label) : references.has(info.label) ? createReferenceHighlight(info.label) : null;
42831
+ if (!highlight) continue;
42684
42832
  setPosition(
42685
42833
  highlight,
42686
42834
  cellRect.left - containerRect.left + container.scrollLeft + 2,
@@ -42735,6 +42883,7 @@ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42735
42883
  editor.view.dom.addEventListener("mousemove", handleMouseMove2);
42736
42884
  editor.view.dom.addEventListener("mouseleave", handleMouseLeave2);
42737
42885
  container.addEventListener(UEDITOR_TABLE_LAYOUT_CHANGE_EVENT, scheduleSync);
42886
+ container.addEventListener("scroll", scheduleSync, scrollListenerOptions);
42738
42887
  window.addEventListener("resize", scheduleSync);
42739
42888
  scheduleSync();
42740
42889
  return () => {
@@ -42745,6 +42894,7 @@ function useFormulaCoordinateOverlay(editor, containerRef, labels) {
42745
42894
  editor.view.dom.removeEventListener("mousemove", handleMouseMove2);
42746
42895
  editor.view.dom.removeEventListener("mouseleave", handleMouseLeave2);
42747
42896
  container.removeEventListener(UEDITOR_TABLE_LAYOUT_CHANGE_EVENT, scheduleSync);
42897
+ container.removeEventListener("scroll", scheduleSync, scrollListenerOptions);
42748
42898
  window.removeEventListener("resize", scheduleSync);
42749
42899
  if (animationFrame != null) window.cancelAnimationFrame(animationFrame);
42750
42900
  clearOverlay();
@@ -43439,7 +43589,7 @@ var MenuBar = ({
43439
43589
  closeInsertMenu();
43440
43590
  };
43441
43591
  const handleLinkSubmit = (url) => {
43442
- editor.chain().focus().setLink({ href: url }).run();
43592
+ applyEditorLink(editor, url);
43443
43593
  closeInsertMenu();
43444
43594
  };
43445
43595
  const insertMenuItems = [
@@ -44191,7 +44341,7 @@ var UEditor = React88.forwardRef(({
44191
44341
  {
44192
44342
  ref: formulaCoordinateOverlayRef,
44193
44343
  "data-ueditor-formula-coordinate-overlay": "",
44194
- className: "pointer-events-none absolute inset-0 z-[21] hidden overflow-visible"
44344
+ className: "pointer-events-none absolute inset-0 z-[35] hidden overflow-visible"
44195
44345
  }
44196
44346
  ),
44197
44347
  formulaRangeHighlight && /* @__PURE__ */ jsx100(
@@ -44199,7 +44349,11 @@ var UEditor = React88.forwardRef(({
44199
44349
  {
44200
44350
  "aria-hidden": "true",
44201
44351
  "data-ueditor-formula-range-highlight": "",
44202
- className: "pointer-events-none absolute z-20 rounded-[2px] border-2 border-primary bg-primary/10",
44352
+ "data-ueditor-formula-range-blocked": formulaRangeHighlight.blocked ? "true" : void 0,
44353
+ className: cn(
44354
+ "pointer-events-none absolute z-20 rounded-[2px] border-2",
44355
+ formulaRangeHighlight.blocked ? "border-destructive bg-destructive/15" : "border-primary bg-primary/10"
44356
+ ),
44203
44357
  style: {
44204
44358
  left: formulaRangeHighlight.left,
44205
44359
  top: formulaRangeHighlight.top,