@bendyline/squisq-editor-react 2.13.20 → 2.13.22

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.
@@ -51,13 +51,13 @@ import {
51
51
  documentTitleFromFileName,
52
52
  resolveBlockVisual,
53
53
  useModalDialog
54
- } from "./chunk-ECS2FFXA.js";
54
+ } from "./chunk-APTVXGK6.js";
55
55
  import {
56
56
  Icon
57
57
  } from "./chunk-UYB5CPRE.js";
58
58
  import {
59
59
  TeleprompterView
60
- } from "./chunk-PQSFLC2R.js";
60
+ } from "./chunk-GI2D2XS5.js";
61
61
 
62
62
  // src/useMonacoLoader.ts
63
63
  import { useEffect, useState } from "react";
@@ -780,7 +780,50 @@ function useMonacoLoader(requestedLanguages = [], options = {}) {
780
780
  }
781
781
 
782
782
  // src/VersionHistoryPanel.tsx
783
- import { useCallback, useEffect as useEffect3, useMemo, useRef as useRef2, useState as useState2 } from "react";
783
+ import {
784
+ useCallback,
785
+ useEffect as useEffect3,
786
+ useLayoutEffect,
787
+ useMemo,
788
+ useRef as useRef2,
789
+ useState as useState2
790
+ } from "react";
791
+
792
+ // src/popoverFit.ts
793
+ var EDGE_MARGIN = 8;
794
+ function clippingBounds(element) {
795
+ const viewport = { left: 0, right: document.documentElement.clientWidth };
796
+ for (let ancestor = element.parentElement; ancestor; ancestor = ancestor.parentElement) {
797
+ if (getComputedStyle(ancestor).overflowX !== "visible") {
798
+ const rect = ancestor.getBoundingClientRect();
799
+ return {
800
+ left: Math.max(rect.left, viewport.left),
801
+ right: Math.min(rect.right, viewport.right)
802
+ };
803
+ }
804
+ }
805
+ return viewport;
806
+ }
807
+ function fitPopoverWithinClip(popover) {
808
+ popover.style.left = "";
809
+ popover.style.right = "";
810
+ popover.style.width = "";
811
+ popover.style.minWidth = "";
812
+ const bounds = clippingBounds(popover);
813
+ const min = bounds.left + EDGE_MARGIN;
814
+ const max = bounds.right - EDGE_MARGIN;
815
+ if (popover.getBoundingClientRect().width > max - min) {
816
+ popover.style.minWidth = "0";
817
+ popover.style.width = `${Math.max(0, max - min)}px`;
818
+ }
819
+ const rect = popover.getBoundingClientRect();
820
+ let shift = rect.right > max ? max - rect.right : 0;
821
+ if (rect.left + shift < min) shift = min - rect.left;
822
+ if (shift !== 0) {
823
+ popover.style.left = `${popover.offsetLeft + shift}px`;
824
+ popover.style.right = "auto";
825
+ }
826
+ }
784
827
 
785
828
  // src/useEscapeDismissal.ts
786
829
  import { useEffect as useEffect2, useRef } from "react";
@@ -877,6 +920,7 @@ function VersionHistoryPanel() {
877
920
  const [state, setState3] = useState2(initialState);
878
921
  const containerRef = useRef2(null);
879
922
  const triggerRef = useRef2(null);
923
+ const popoverRef = useRef2(null);
880
924
  const close = useCallback(() => setOpen(false), []);
881
925
  useEscapeDismissal(open, close, triggerRef);
882
926
  const refresh = useCallback(async () => {
@@ -972,6 +1016,9 @@ function VersionHistoryPanel() {
972
1016
  );
973
1017
  const hasDiff = state.selected !== null;
974
1018
  const diffTheme = colorScheme === "dark" ? "vs-dark" : "vs";
1019
+ useLayoutEffect(() => {
1020
+ if (open && popoverRef.current) fitPopoverWithinClip(popoverRef.current);
1021
+ }, [open, hasDiff]);
975
1022
  if (!versioning) return null;
976
1023
  return /* @__PURE__ */ jsxs("div", { className: "squisq-version-history", ref: containerRef, children: [
977
1024
  /* @__PURE__ */ jsx(
@@ -990,6 +1037,7 @@ function VersionHistoryPanel() {
990
1037
  open && /* @__PURE__ */ jsxs(
991
1038
  "div",
992
1039
  {
1040
+ ref: popoverRef,
993
1041
  className: `squisq-version-history-popover${hasDiff ? " squisq-version-history-popover--with-diff" : ""}`,
994
1042
  role: "dialog",
995
1043
  "aria-label": "Version history",
@@ -1105,7 +1153,7 @@ function VersionHistoryPanel() {
1105
1153
  }
1106
1154
 
1107
1155
  // src/ThemePicker.tsx
1108
- import { useCallback as useCallback2, useEffect as useEffect4, useId, useLayoutEffect, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
1156
+ import { useCallback as useCallback2, useEffect as useEffect4, useId, useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
1109
1157
  import { createPortal } from "react-dom";
1110
1158
  import {
1111
1159
  getThemeSummaries,
@@ -1267,7 +1315,7 @@ function ThemePicker({
1267
1315
  setOpen(false);
1268
1316
  triggerRef.current?.focus();
1269
1317
  };
1270
- useLayoutEffect(() => {
1318
+ useLayoutEffect2(() => {
1271
1319
  if (!open) return;
1272
1320
  const options = popoverRef.current?.querySelectorAll('[role="option"]');
1273
1321
  const selected = popoverRef.current?.querySelector('[aria-selected="true"]');
@@ -2426,7 +2474,7 @@ import {
2426
2474
  useState as useState7,
2427
2475
  useMemo as useMemo6,
2428
2476
  useEffect as useEffect7,
2429
- useLayoutEffect as useLayoutEffect2,
2477
+ useLayoutEffect as useLayoutEffect3,
2430
2478
  useRef as useRef6
2431
2479
  } from "react";
2432
2480
  import { createPortal as createPortal2 } from "react-dom";
@@ -3332,12 +3380,14 @@ var DISPLAY_MODE_OPTIONS = [
3332
3380
  {
3333
3381
  key: "slideshow",
3334
3382
  label: "Slideshow",
3383
+ shortLabel: "Slides",
3335
3384
  icon: "fa-solid fa-images",
3336
3385
  summary: "Present designed slides one at a time."
3337
3386
  },
3338
3387
  {
3339
3388
  key: "flashcards",
3340
3389
  label: "Flashcards",
3390
+ shortLabel: "Cards",
3341
3391
  icon: "fa-solid fa-clone",
3342
3392
  summary: "Study with progressive reveals and quizzes."
3343
3393
  },
@@ -3356,12 +3406,14 @@ var DISPLAY_MODE_OPTIONS = [
3356
3406
  {
3357
3407
  key: "dashboard",
3358
3408
  label: "Dashboard",
3409
+ shortLabel: "Board",
3359
3410
  icon: "fa-solid fa-table-cells-large",
3360
3411
  summary: "See every block arranged on one canvas."
3361
3412
  },
3362
3413
  {
3363
3414
  key: "page",
3364
3415
  label: "Document",
3416
+ shortLabel: "Doc",
3365
3417
  icon: "fa-solid fa-align-left",
3366
3418
  summary: "Read a clean, text-first document."
3367
3419
  },
@@ -3375,6 +3427,10 @@ var DISPLAY_MODE_OPTIONS = [
3375
3427
  function displayModeLabel(mode) {
3376
3428
  return DISPLAY_MODE_OPTIONS.find((option) => option.key === mode)?.label ?? "Slideshow";
3377
3429
  }
3430
+ function displayModeShortLabel(mode) {
3431
+ const option = DISPLAY_MODE_OPTIONS.find((candidate) => candidate.key === mode);
3432
+ return option?.shortLabel ?? option?.label ?? "Slides";
3433
+ }
3378
3434
  var TRANSFORM_STYLE_OPTIONS = [
3379
3435
  { key: "", label: "None" },
3380
3436
  ...getTransformStyleSummaries2().map((s) => ({ key: s.id, label: s.name }))
@@ -3800,7 +3856,7 @@ function PreviewToolbarControls({
3800
3856
  setPopoverOpen(false);
3801
3857
  setPopoverAnchor(null);
3802
3858
  }, []);
3803
- useLayoutEffect2(() => {
3859
+ useLayoutEffect3(() => {
3804
3860
  const root = rootRef.current;
3805
3861
  const probe = probeRef.current;
3806
3862
  if (!root || !probe) return;
@@ -3841,7 +3897,7 @@ function PreviewToolbarControls({
3841
3897
  document.addEventListener("mousedown", handler);
3842
3898
  return () => document.removeEventListener("mousedown", handler);
3843
3899
  }, [closePopover, popoverOpen]);
3844
- useLayoutEffect2(() => {
3900
+ useLayoutEffect3(() => {
3845
3901
  if (!popoverOpen) return;
3846
3902
  updatePopoverPosition();
3847
3903
  }, [popoverOpen, updatePopoverPosition, visibleCount]);
@@ -4289,7 +4345,7 @@ function PreviewSelect({
4289
4345
  }
4290
4346
 
4291
4347
  // src/toolbar/ViewModeMenu.tsx
4292
- import { useCallback as useCallback8, useEffect as useEffect8, useId as useId2, useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState8 } from "react";
4348
+ import { useCallback as useCallback8, useEffect as useEffect8, useId as useId2, useLayoutEffect as useLayoutEffect4, useRef as useRef7, useState as useState8 } from "react";
4293
4349
  import { createPortal as createPortal3 } from "react-dom";
4294
4350
  import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
4295
4351
  var VIEW_MODE_MENU_WIDTH = 340;
@@ -4363,7 +4419,7 @@ function ViewModeMenu({
4363
4419
  window.removeEventListener("scroll", updatePosition, true);
4364
4420
  };
4365
4421
  }, [closeMenu, open, updatePosition]);
4366
- useLayoutEffect3(() => {
4422
+ useLayoutEffect4(() => {
4367
4423
  if (!open || !anchor) return;
4368
4424
  const checked = menuRef.current?.querySelector('[aria-checked="true"]');
4369
4425
  const first = menuRef.current?.querySelector('[role="menuitemradio"]');
@@ -4475,14 +4531,14 @@ function PreviewModeMenu({ openRequest = 0 }) {
4475
4531
  }
4476
4532
 
4477
4533
  // src/ViewMenuPanel.tsx
4478
- import { useCallback as useCallback10, useEffect as useEffect11, useLayoutEffect as useLayoutEffect6, useRef as useRef11, useState as useState13 } from "react";
4534
+ import { useCallback as useCallback10, useEffect as useEffect12, useLayoutEffect as useLayoutEffect7, useRef as useRef11, useState as useState13 } from "react";
4479
4535
  import { createPortal as createPortal6 } from "react-dom";
4480
4536
 
4481
4537
  // src/useAnchoredPopover.ts
4482
- import { useLayoutEffect as useLayoutEffect4, useState as useState9 } from "react";
4538
+ import { useLayoutEffect as useLayoutEffect5, useState as useState9 } from "react";
4483
4539
  function useAnchoredPopover(open, triggerRef, popoverRef) {
4484
4540
  const [style, setStyle] = useState9({ position: "fixed", visibility: "hidden" });
4485
- useLayoutEffect4(() => {
4541
+ useLayoutEffect5(() => {
4486
4542
  if (!open) return;
4487
4543
  const position = () => {
4488
4544
  const trigger = triggerRef.current;
@@ -4667,6 +4723,34 @@ function proofingDecorationById(state, findingId) {
4667
4723
  return { findingId: spec.findingId, category: spec.category, from: hit.from, to: hit.to };
4668
4724
  }
4669
4725
 
4726
+ // src/proofing/proofingTap.ts
4727
+ var lastPointerDownType = "";
4728
+ var trackers = 0;
4729
+ function notePointerDown(event) {
4730
+ lastPointerDownType = event.pointerType ?? "";
4731
+ }
4732
+ function trackTapPointers() {
4733
+ if (trackers === 0) window.addEventListener("pointerdown", notePointerDown, true);
4734
+ trackers += 1;
4735
+ let released = false;
4736
+ return () => {
4737
+ if (released) return;
4738
+ released = true;
4739
+ trackers -= 1;
4740
+ if (trackers === 0) window.removeEventListener("pointerdown", notePointerDown, true);
4741
+ };
4742
+ }
4743
+ function isTapClick(event) {
4744
+ const own = event.pointerType;
4745
+ const type = typeof own === "string" ? own : event.detail > 0 ? lastPointerDownType : "";
4746
+ return type === "touch" || type === "pen";
4747
+ }
4748
+ function proofingTapTarget(event) {
4749
+ if (!isTapClick(event)) return null;
4750
+ const target = event.target;
4751
+ return target?.closest?.("[data-proof-id]") ?? null;
4752
+ }
4753
+
4670
4754
  // src/proofing/writeViewText.ts
4671
4755
  function collectTextblockRuns(doc) {
4672
4756
  const runs = [];
@@ -4789,7 +4873,15 @@ var engineIgnoreOwner = /* @__PURE__ */ new WeakMap();
4789
4873
  function categoryEnabled(finding, filter) {
4790
4874
  return finding.category === "spelling" ? filter.spelling : filter.grammar;
4791
4875
  }
4792
- function useProofing() {
4876
+ function findSquiggle(root, findingId) {
4877
+ for (const span of root.querySelectorAll("[data-proof-id]")) {
4878
+ if (span.getAttribute("data-proof-id") === findingId) return span;
4879
+ }
4880
+ return null;
4881
+ }
4882
+ function useProofing({
4883
+ canOpenLinks = false
4884
+ } = {}) {
4793
4885
  const {
4794
4886
  proofing,
4795
4887
  proofingDefaultEnabled,
@@ -4833,6 +4925,11 @@ function useProofing() {
4833
4925
  const hoverCloseTimerRef = useRef8(void 0);
4834
4926
  const hoveredFindingRef = useRef8(null);
4835
4927
  const hoverHeldRef = useRef8(false);
4928
+ const tapCardRef = useRef8(null);
4929
+ const tapDismissedRef = useRef8(null);
4930
+ const canOpenLinksRef = useRef8(canOpenLinks);
4931
+ canOpenLinksRef.current = canOpenLinks;
4932
+ const carryHoverRef = useRef8(() => void 0);
4836
4933
  const settings = useMemo7(() => readProofingSettings(markdownDoc?.frontmatter), [markdownDoc]);
4837
4934
  const anyCategoryEnabled = proofingSpellingEnabled || proofingGrammarEnabled;
4838
4935
  const enabled = proofing != null && editorMode === "markdown" && anyCategoryEnabled && (sessionEnabled ?? settings.enabled ?? proofingDefaultEnabled);
@@ -4950,10 +5047,18 @@ function useProofing() {
4950
5047
  });
4951
5048
  kept.push(finding);
4952
5049
  }
5050
+ const openId = hoveredFindingRef.current;
5051
+ const openRange = openId === null ? null : proofingDecorationById(editor.state, openId);
4953
5052
  lastWriteDecorationsRef.current = decorated;
4954
5053
  updateTiptapProofingDecorations(editor, decorated);
4955
5054
  setFindings(kept);
4956
5055
  setFindingsView("wysiwyg");
5056
+ if (openId !== null) {
5057
+ const successor = openRange ? decorated.find(
5058
+ (decoration) => decoration.from === openRange.from && decoration.to === openRange.to && decoration.category === openRange.category
5059
+ ) : void 0;
5060
+ carryHoverRef.current(successor?.findingId ?? null);
5061
+ }
4957
5062
  } else if (view === "raw") {
4958
5063
  const editor = monacoRef.current;
4959
5064
  const model2 = editor?.getModel();
@@ -5126,7 +5231,14 @@ function useProofing() {
5126
5231
  const hit = proofingDecorationAt(editor.state, pos.pos);
5127
5232
  if (!hit) return;
5128
5233
  event.preventDefault();
5129
- openMenuRef.current({ findingId: hit.findingId, x: event.clientX, y: event.clientY });
5234
+ const target = event.target;
5235
+ const linkHref = canOpenLinksRef.current && isTapClick(event) ? target?.closest?.("a[href]")?.getAttribute("href") : null;
5236
+ openMenuRef.current({
5237
+ findingId: hit.findingId,
5238
+ x: event.clientX,
5239
+ y: event.clientY,
5240
+ ...linkHref ? { linkHref } : {}
5241
+ });
5130
5242
  };
5131
5243
  const onKeyDown = (event) => {
5132
5244
  if (event.key !== "ContextMenu" && !(event.shiftKey && event.key === "F10")) return;
@@ -5154,8 +5266,21 @@ function useProofing() {
5154
5266
  cancelHoverTimers();
5155
5267
  hoveredFindingRef.current = null;
5156
5268
  hoverHeldRef.current = false;
5269
+ tapCardRef.current = null;
5157
5270
  setHoverAnchor(null);
5158
5271
  }, [cancelHoverTimers]);
5272
+ carryHoverRef.current = (successorId) => {
5273
+ if (successorId === null) {
5274
+ closeHover();
5275
+ return;
5276
+ }
5277
+ const retired = hoveredFindingRef.current;
5278
+ hoveredFindingRef.current = successorId;
5279
+ if (tapCardRef.current !== null) tapCardRef.current = successorId;
5280
+ setHoverAnchor(
5281
+ (current) => current && current.findingId === retired ? { ...current, findingId: successorId } : current
5282
+ );
5283
+ };
5159
5284
  const scheduleHoverClose = useCallback9(() => {
5160
5285
  if (hoverOpenTimerRef.current !== void 0) {
5161
5286
  window.clearTimeout(hoverOpenTimerRef.current);
@@ -5166,6 +5291,7 @@ function useProofing() {
5166
5291
  hoverCloseTimerRef.current = void 0;
5167
5292
  if (hoverHeldRef.current) return;
5168
5293
  hoveredFindingRef.current = null;
5294
+ tapCardRef.current = null;
5169
5295
  setHoverAnchor(null);
5170
5296
  }, HOVER_CLOSE_DELAY_MS);
5171
5297
  }, []);
@@ -5185,7 +5311,8 @@ function useProofing() {
5185
5311
  const editor = tiptapEditor;
5186
5312
  if (!editor || editor.isDestroyed) return;
5187
5313
  const dom = editor.view.dom;
5188
- const onMouseMove = (event) => {
5314
+ const onPointerMove = (event) => {
5315
+ if (event.pointerType === "touch" || tapCardRef.current !== null) return;
5189
5316
  const target = event.target;
5190
5317
  const span = target?.closest?.("[data-proof-id]");
5191
5318
  const findingId = span?.getAttribute("data-proof-id") ?? null;
@@ -5206,25 +5333,117 @@ function useProofing() {
5206
5333
  setHoverAnchor(null);
5207
5334
  hoverOpenTimerRef.current = window.setTimeout(() => {
5208
5335
  hoverOpenTimerRef.current = void 0;
5209
- const rect = span.getBoundingClientRect();
5210
- setHoverAnchor({ findingId, left: rect.left, top: rect.top, bottom: rect.bottom });
5336
+ const dwelt = hoveredFindingRef.current;
5337
+ const live = dwelt === null ? null : findSquiggle(dom, dwelt);
5338
+ if (dwelt === null || !live) {
5339
+ hoveredFindingRef.current = null;
5340
+ return;
5341
+ }
5342
+ const rect = live.getBoundingClientRect();
5343
+ setHoverAnchor({
5344
+ findingId: dwelt,
5345
+ left: rect.left,
5346
+ top: rect.top,
5347
+ bottom: rect.bottom,
5348
+ trigger: "hover"
5349
+ });
5211
5350
  }, HOVER_DELAY_MS);
5212
5351
  };
5213
- const onMouseLeave = () => {
5352
+ const onPointerLeave = (event) => {
5353
+ if (event.pointerType === "touch" || tapCardRef.current !== null) return;
5214
5354
  if (hoveredFindingRef.current !== null) scheduleHoverClose();
5215
5355
  };
5216
- dom.addEventListener("mousemove", onMouseMove);
5217
- dom.addEventListener("mouseleave", onMouseLeave);
5356
+ const onPress = (event) => {
5357
+ tapDismissedRef.current = null;
5358
+ const open = tapCardRef.current;
5359
+ if (open === null) return;
5360
+ const target = event.target;
5361
+ if (target?.closest?.(".squisq-proof-tooltip")) return;
5362
+ const pressed = target?.closest?.("[data-proof-id]");
5363
+ if (pressed && dom.contains(pressed) && pressed.getAttribute("data-proof-id") === open) {
5364
+ tapDismissedRef.current = open;
5365
+ }
5366
+ closeHover();
5367
+ };
5368
+ const onClick = (event) => {
5369
+ const span = proofingTapTarget(event);
5370
+ const findingId = span?.getAttribute("data-proof-id");
5371
+ if (!span || !findingId) return;
5372
+ if (tapDismissedRef.current === findingId) {
5373
+ tapDismissedRef.current = null;
5374
+ return;
5375
+ }
5376
+ const linkHref = canOpenLinksRef.current ? span.closest("a[href]")?.getAttribute("href") : null;
5377
+ if (linkHref) event.preventDefault();
5378
+ cancelHoverTimers();
5379
+ hoverHeldRef.current = false;
5380
+ hoveredFindingRef.current = findingId;
5381
+ tapCardRef.current = findingId;
5382
+ const rect = span.getBoundingClientRect();
5383
+ setHoverAnchor({
5384
+ findingId,
5385
+ left: rect.left,
5386
+ top: rect.top,
5387
+ bottom: rect.bottom,
5388
+ trigger: "tap",
5389
+ ...linkHref ? { linkHref } : {}
5390
+ });
5391
+ };
5392
+ const onScroll = () => {
5393
+ if (tapCardRef.current === null) closeHover();
5394
+ };
5395
+ const onKeyDown = () => {
5396
+ if (tapCardRef.current !== null) closeHover();
5397
+ };
5398
+ const releaseTapPointers = trackTapPointers();
5399
+ dom.addEventListener("pointermove", onPointerMove);
5400
+ dom.addEventListener("pointerleave", onPointerLeave);
5218
5401
  dom.addEventListener("mousedown", closeHover);
5219
- window.addEventListener("scroll", closeHover, true);
5402
+ dom.addEventListener("click", onClick);
5403
+ dom.addEventListener("keydown", onKeyDown);
5404
+ document.addEventListener("pointerdown", onPress, true);
5405
+ window.addEventListener("scroll", onScroll, true);
5220
5406
  return () => {
5221
- dom.removeEventListener("mousemove", onMouseMove);
5222
- dom.removeEventListener("mouseleave", onMouseLeave);
5407
+ releaseTapPointers();
5408
+ dom.removeEventListener("pointermove", onPointerMove);
5409
+ dom.removeEventListener("pointerleave", onPointerLeave);
5223
5410
  dom.removeEventListener("mousedown", closeHover);
5224
- window.removeEventListener("scroll", closeHover, true);
5411
+ dom.removeEventListener("click", onClick);
5412
+ dom.removeEventListener("keydown", onKeyDown);
5413
+ document.removeEventListener("pointerdown", onPress, true);
5414
+ window.removeEventListener("scroll", onScroll, true);
5415
+ tapDismissedRef.current = null;
5225
5416
  closeHover();
5226
5417
  };
5227
5418
  }, [tiptapEditor, enabled, status, cancelHoverTimers, closeHover, scheduleHoverClose]);
5419
+ const tapCardId = hoverAnchor?.trigger === "tap" ? hoverAnchor.findingId : null;
5420
+ useEffect9(() => {
5421
+ if (tapCardId === null) return;
5422
+ let frame = 0;
5423
+ const follow = () => {
5424
+ frame = 0;
5425
+ const editor = tiptapRef.current;
5426
+ const span = editor && !editor.isDestroyed ? findSquiggle(editor.view.dom, tapCardId) : null;
5427
+ if (!span) {
5428
+ closeHover();
5429
+ return;
5430
+ }
5431
+ const rect = span.getBoundingClientRect();
5432
+ setHoverAnchor(
5433
+ (current) => current && current.findingId === tapCardId && (current.left !== rect.left || current.top !== rect.top || current.bottom !== rect.bottom) ? { ...current, left: rect.left, top: rect.top, bottom: rect.bottom } : current
5434
+ );
5435
+ };
5436
+ const schedule2 = () => {
5437
+ if (frame === 0) frame = requestAnimationFrame(follow);
5438
+ };
5439
+ window.addEventListener("scroll", schedule2, true);
5440
+ window.addEventListener("resize", schedule2);
5441
+ return () => {
5442
+ if (frame !== 0) cancelAnimationFrame(frame);
5443
+ window.removeEventListener("scroll", schedule2, true);
5444
+ window.removeEventListener("resize", schedule2);
5445
+ };
5446
+ }, [tapCardId, closeHover]);
5228
5447
  useEffect9(() => {
5229
5448
  if (!enabled || status !== "ready") return;
5230
5449
  const editor = monacoEditor;
@@ -5599,6 +5818,7 @@ function ProofingMenu({
5599
5818
  onAddToAppDictionary,
5600
5819
  onAddToDocWordList,
5601
5820
  canAddToAppDictionary,
5821
+ onOpenLink,
5602
5822
  onClose
5603
5823
  }) {
5604
5824
  const menuRef = useRef9(null);
@@ -5609,16 +5829,16 @@ function ProofingMenu({
5609
5829
  }, [anchor]);
5610
5830
  useEscapeDismissal(true, onClose);
5611
5831
  useEffect10(() => {
5612
- const onMouse = (event) => {
5832
+ const onPress = (event) => {
5613
5833
  if (!menuRef.current?.contains(event.target)) onClose();
5614
5834
  };
5615
5835
  const onScrollOrResize = () => onClose();
5616
- const id = requestAnimationFrame(() => document.addEventListener("mousedown", onMouse));
5836
+ const id = requestAnimationFrame(() => document.addEventListener("pointerdown", onPress));
5617
5837
  window.addEventListener("scroll", onScrollOrResize, true);
5618
5838
  window.addEventListener("resize", onScrollOrResize);
5619
5839
  return () => {
5620
5840
  cancelAnimationFrame(id);
5621
- document.removeEventListener("mousedown", onMouse);
5841
+ document.removeEventListener("pointerdown", onPress);
5622
5842
  window.removeEventListener("scroll", onScrollOrResize, true);
5623
5843
  window.removeEventListener("resize", onScrollOrResize);
5624
5844
  };
@@ -5643,7 +5863,8 @@ function ProofingMenu({
5643
5863
  label: "Add to document word list",
5644
5864
  run: onAddToDocWordList
5645
5865
  }
5646
- ] : []
5866
+ ] : [],
5867
+ ...onOpenLink ? [{ key: "open-link", label: "Open link", run: onOpenLink }] : []
5647
5868
  ];
5648
5869
  useEffect10(() => {
5649
5870
  const items = menuRef.current?.querySelectorAll('[role="menuitem"]');
@@ -5696,23 +5917,35 @@ function ProofingMenu({
5696
5917
  }
5697
5918
 
5698
5919
  // src/proofing/ProofingTooltip.tsx
5699
- import { useLayoutEffect as useLayoutEffect5, useRef as useRef10, useState as useState12 } from "react";
5920
+ import { useEffect as useEffect11, useLayoutEffect as useLayoutEffect6, useRef as useRef10, useState as useState12 } from "react";
5700
5921
  import { createPortal as createPortal5 } from "react-dom";
5701
5922
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
5702
5923
  var MAX_SUGGESTIONS2 = 3;
5924
+ function visibleBounds() {
5925
+ const viewport = window.visualViewport;
5926
+ if (!viewport) return { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight };
5927
+ return {
5928
+ top: viewport.offsetTop,
5929
+ left: viewport.offsetLeft,
5930
+ width: viewport.width,
5931
+ height: viewport.height
5932
+ };
5933
+ }
5703
5934
  function computeStyle2(anchor, element) {
5704
5935
  const rect = element?.getBoundingClientRect();
5705
5936
  const width = rect?.width ?? 240;
5706
5937
  const height = rect?.height ?? 72;
5707
5938
  const margin = 8;
5708
5939
  const gap = 6;
5940
+ const view = visibleBounds();
5709
5941
  let top = anchor.bottom + gap;
5710
- if (top + height + margin > window.innerHeight && anchor.top - height - gap >= margin) {
5942
+ if (top + height + margin > view.top + view.height && anchor.top - height - gap >= view.top + margin) {
5711
5943
  top = anchor.top - height - gap;
5712
5944
  }
5945
+ const minLeft = view.left + margin;
5713
5946
  const left = Math.min(
5714
- Math.max(margin, anchor.left),
5715
- Math.max(margin, window.innerWidth - width - margin)
5947
+ Math.max(minLeft, anchor.left),
5948
+ Math.max(minLeft, view.left + view.width - width - margin)
5716
5949
  );
5717
5950
  return { position: "fixed", top, left, zIndex: 9998 };
5718
5951
  }
@@ -5727,13 +5960,26 @@ function ProofingTooltip({
5727
5960
  canAddToAppDictionary,
5728
5961
  onMore,
5729
5962
  onHold,
5730
- onRelease
5963
+ onRelease,
5964
+ linkHref,
5965
+ onOpenLink
5731
5966
  }) {
5732
5967
  const tooltipRef = useRef10(null);
5733
5968
  const [style, setStyle] = useState12(() => computeStyle2(anchor));
5734
- useLayoutEffect5(() => {
5969
+ useLayoutEffect6(() => {
5735
5970
  setStyle(computeStyle2(anchor, tooltipRef.current));
5736
- }, [anchor, finding.id]);
5971
+ }, [anchor, finding.id, linkHref]);
5972
+ useEffect11(() => {
5973
+ const viewport = window.visualViewport;
5974
+ if (!viewport) return;
5975
+ const place = () => setStyle(computeStyle2(anchor, tooltipRef.current));
5976
+ viewport.addEventListener("resize", place);
5977
+ viewport.addEventListener("scroll", place);
5978
+ return () => {
5979
+ viewport.removeEventListener("resize", place);
5980
+ viewport.removeEventListener("scroll", place);
5981
+ };
5982
+ }, [anchor]);
5737
5983
  const suggestions = finding.suggestions.slice(0, MAX_SUGGESTIONS2);
5738
5984
  const extra = finding.suggestions.length - suggestions.length;
5739
5985
  const showAddWord = finding.category === "spelling";
@@ -5744,6 +5990,7 @@ function ProofingTooltip({
5744
5990
  ref: tooltipRef,
5745
5991
  className: "squisq-proof-tooltip",
5746
5992
  "data-theme": colorScheme,
5993
+ "data-trigger": anchor.trigger,
5747
5994
  "data-proof-tooltip-id": finding.id,
5748
5995
  role: "group",
5749
5996
  "aria-label": `${PROOF_CATEGORY_LABELS[finding.category]} suggestion`,
@@ -5805,6 +6052,10 @@ function ProofingTooltip({
5805
6052
  children: "Add to document word list"
5806
6053
  }
5807
6054
  )
6055
+ ] }),
6056
+ linkHref && /* @__PURE__ */ jsxs9("div", { className: "squisq-proof-tooltip-link", children: [
6057
+ /* @__PURE__ */ jsx11("button", { type: "button", className: "squisq-proof-tooltip-action", onClick: onOpenLink, children: "Open link" }),
6058
+ /* @__PURE__ */ jsx11("span", { className: "squisq-proof-tooltip-link-href", children: linkHref })
5808
6059
  ] })
5809
6060
  ]
5810
6061
  }
@@ -5819,11 +6070,13 @@ var ProofingStateContext = createContext3(null);
5819
6070
  function useProofingState() {
5820
6071
  return useContext3(ProofingStateContext);
5821
6072
  }
5822
- function ProofingRoot({ children }) {
6073
+ function ProofingRoot({ children, onLinkClick }) {
5823
6074
  const { colorScheme } = useEditorContext();
5824
- const state = useProofing();
6075
+ const state = useProofing({ canOpenLinks: onLinkClick != null });
5825
6076
  const menuFinding = state?.menuAnchor != null ? state.findings.find((finding) => finding.id === state.menuAnchor?.findingId) ?? null : null;
5826
6077
  const hoverFinding = state?.hoverAnchor != null && !state.menuAnchor ? state.findings.find((finding) => finding.id === state.hoverAnchor?.findingId) ?? null : null;
6078
+ const menuLink = state?.menuAnchor?.linkHref;
6079
+ const hoverLink = state?.hoverAnchor?.linkHref;
5827
6080
  return /* @__PURE__ */ jsxs10(ProofingStateContext.Provider, { value: state, children: [
5828
6081
  children,
5829
6082
  state && state.menuAnchor && menuFinding && /* @__PURE__ */ jsx12(
@@ -5832,6 +6085,10 @@ function ProofingRoot({ children }) {
5832
6085
  anchor: state.menuAnchor,
5833
6086
  finding: menuFinding,
5834
6087
  colorScheme,
6088
+ onOpenLink: menuLink ? () => {
6089
+ state.closeMenu();
6090
+ onLinkClick?.(menuLink);
6091
+ } : void 0,
5835
6092
  onApply: (index2) => {
5836
6093
  state.applySuggestion(menuFinding.id, index2);
5837
6094
  state.closeMenu();
@@ -5875,7 +6132,12 @@ function ProofingRoot({ children }) {
5875
6132
  state.closeHover();
5876
6133
  },
5877
6134
  canAddToAppDictionary: state.canAddToAppDictionary,
5878
- onMore: (x, y) => state.openMenu({ findingId: hoverFinding.id, x, y }),
6135
+ linkHref: hoverLink,
6136
+ onOpenLink: () => {
6137
+ state.closeHover();
6138
+ if (hoverLink) onLinkClick?.(hoverLink);
6139
+ },
6140
+ onMore: (x, y) => state.openMenu({ findingId: hoverFinding.id, x, y, linkHref: hoverLink }),
5879
6141
  onHold: state.holdHover,
5880
6142
  onRelease: state.releaseHover
5881
6143
  }
@@ -5908,12 +6170,12 @@ function ViewMenuPanel() {
5908
6170
  const popoverStyle = useAnchoredPopover(open, triggerRef, menuRef);
5909
6171
  const close = useCallback10(() => setOpen(false), []);
5910
6172
  useEscapeDismissal(open, close, triggerRef);
5911
- useLayoutEffect6(() => {
6173
+ useLayoutEffect7(() => {
5912
6174
  if (!open || popoverStyle.visibility === "hidden") return;
5913
6175
  const items = menuRef.current?.querySelectorAll('[role^="menuitem"]');
5914
6176
  if (items?.length) items[initialFocus.current === "last" ? items.length - 1 : 0]?.focus();
5915
6177
  }, [open, popoverStyle.visibility]);
5916
- useEffect11(() => {
6178
+ useEffect12(() => {
5917
6179
  if (!open) return;
5918
6180
  const handler = (e2) => {
5919
6181
  if (containerRef.current && !containerRef.current.contains(e2.target) && !menuRef.current?.contains(e2.target)) {
@@ -6101,7 +6363,7 @@ function MenuRadioGroup({
6101
6363
  }
6102
6364
 
6103
6365
  // src/TransformMenu.tsx
6104
- import { useCallback as useCallback11, useEffect as useEffect12, useMemo as useMemo8, useRef as useRef12, useState as useState14 } from "react";
6366
+ import { useCallback as useCallback11, useEffect as useEffect13, useMemo as useMemo8, useRef as useRef12, useState as useState14 } from "react";
6105
6367
  import {
6106
6368
  MARKDOWN_SOURCE_TRANSFORMS,
6107
6369
  DEFAULT_WRAP_WIDTH,
@@ -6146,7 +6408,7 @@ function TransformMenu() {
6146
6408
  const triggerRef = useRef12(null);
6147
6409
  const close = useCallback11(() => setOpen(false), []);
6148
6410
  useEscapeDismissal(open, close, triggerRef);
6149
- useEffect12(() => {
6411
+ useEffect13(() => {
6150
6412
  if (!open) return;
6151
6413
  const handler = (e2) => {
6152
6414
  if (containerRef.current && !containerRef.current.contains(e2.target)) {
@@ -6156,7 +6418,7 @@ function TransformMenu() {
6156
6418
  document.addEventListener("mousedown", handler);
6157
6419
  return () => document.removeEventListener("mousedown", handler);
6158
6420
  }, [open]);
6159
- useEffect12(() => {
6421
+ useEffect13(() => {
6160
6422
  if (open) setStatus("");
6161
6423
  }, [open]);
6162
6424
  const wrapState = useMemo8(
@@ -6305,7 +6567,7 @@ import {
6305
6567
  createContext as createContext4,
6306
6568
  useCallback as useCallback12,
6307
6569
  useContext as useContext4,
6308
- useEffect as useEffect13,
6570
+ useEffect as useEffect14,
6309
6571
  useMemo as useMemo9,
6310
6572
  useState as useState15
6311
6573
  } from "react";
@@ -6377,7 +6639,7 @@ function CustomTemplateProvider({
6377
6639
  const [libraryTemplates, setLibraryTemplates] = useState15(
6378
6640
  () => listLibraryTemplates()
6379
6641
  );
6380
- useEffect13(() => {
6642
+ useEffect14(() => {
6381
6643
  setLibraryTemplates(listLibraryTemplates());
6382
6644
  const onStorage = (event) => {
6383
6645
  if (event.key === LIBRARY_STORAGE_KEY) setLibraryTemplates(listLibraryTemplates());
@@ -6447,7 +6709,7 @@ function useCustomTemplates() {
6447
6709
  }
6448
6710
 
6449
6711
  // src/TemplatePicker.tsx
6450
- import { useEffect as useEffect14, useId as useId3, useMemo as useMemo11, useRef as useRef13, useState as useState16 } from "react";
6712
+ import { useEffect as useEffect15, useId as useId3, useMemo as useMemo11, useRef as useRef13, useState as useState16 } from "react";
6451
6713
  import { createPortal as createPortal7 } from "react-dom";
6452
6714
  import { TEMPLATE_METADATA, resolveTemplateName } from "@bendyline/squisq/doc";
6453
6715
  import { extractPlainText as extractPlainText2 } from "@bendyline/squisq/markdown";
@@ -7244,6 +7506,90 @@ var TEMPLATE_ENTRIES = [
7244
7506
  /* @__PURE__ */ jsx19("rect", { x: 39, y: 26, width: 12, height: 3, rx: 1, fill: F2 })
7245
7507
  ] })
7246
7508
  },
7509
+ {
7510
+ name: "stats",
7511
+ label: "Key Numbers",
7512
+ description: "Sets two to six key figures side by side, each counting up over what it measures.",
7513
+ icon: /* @__PURE__ */ jsxs14(TemplateIcon, { children: [
7514
+ /* @__PURE__ */ jsx19("rect", { x: 4, y: 5, width: 30, height: 4, rx: 1, fill: F2 }),
7515
+ [4, 22, 40].map((x) => /* @__PURE__ */ jsxs14("g", { children: [
7516
+ /* @__PURE__ */ jsx19("rect", { x, y: 15, width: 12, height: 9, rx: 1.5, fill: FA }),
7517
+ /* @__PURE__ */ jsx19("rect", { x, y: 28, width: 13, height: 2.5, rx: 1, fill: F2 }),
7518
+ /* @__PURE__ */ jsx19("rect", { x, y: 33, width: 9, height: 2.5, rx: 1, fill: F1 })
7519
+ ] }, x))
7520
+ ] })
7521
+ },
7522
+ {
7523
+ name: "pyramid",
7524
+ label: "Pyramid",
7525
+ description: "Stacks a hierarchy as a pyramid, the apex first and the base last.",
7526
+ icon: /* @__PURE__ */ jsxs14(TemplateIcon, { children: [
7527
+ /* @__PURE__ */ jsx19("path", { d: "M 18 6 L 23 15 L 13 15 Z", fill: FA }),
7528
+ /* @__PURE__ */ jsx19("path", { d: "M 12.2 16.5 L 23.8 16.5 L 29 26 L 7 26 Z", fill: FA, opacity: 0.65 }),
7529
+ /* @__PURE__ */ jsx19("path", { d: "M 6.2 27.5 L 29.8 27.5 L 35 37 L 1 37 Z", fill: FA, opacity: 0.35 }),
7530
+ /* @__PURE__ */ jsx19("rect", { x: 39, y: 9, width: 13, height: 3, rx: 1, fill: F2 }),
7531
+ /* @__PURE__ */ jsx19("rect", { x: 39, y: 20, width: 13, height: 3, rx: 1, fill: F2 }),
7532
+ /* @__PURE__ */ jsx19("rect", { x: 39, y: 31, width: 13, height: 3, rx: 1, fill: F2 })
7533
+ ] })
7534
+ },
7535
+ {
7536
+ name: "funnel",
7537
+ label: "Funnel",
7538
+ description: "Draws stages that narrow as a funnel, the widest first and the outcome last.",
7539
+ icon: /* @__PURE__ */ jsxs14(TemplateIcon, { children: [
7540
+ /* @__PURE__ */ jsx19("path", { d: "M 2 6 L 34 6 L 30 15 L 6 15 Z", fill: FA, opacity: 0.35 }),
7541
+ /* @__PURE__ */ jsx19("path", { d: "M 6.7 16.5 L 29.3 16.5 L 25.5 26 L 10.5 26 Z", fill: FA, opacity: 0.65 }),
7542
+ /* @__PURE__ */ jsx19("path", { d: "M 11.2 27.5 L 24.8 27.5 L 21.5 37 L 14.5 37 Z", fill: FA }),
7543
+ /* @__PURE__ */ jsx19("rect", { x: 39, y: 9, width: 13, height: 3, rx: 1, fill: F2 }),
7544
+ /* @__PURE__ */ jsx19("rect", { x: 39, y: 20, width: 13, height: 3, rx: 1, fill: F2 }),
7545
+ /* @__PURE__ */ jsx19("rect", { x: 39, y: 31, width: 13, height: 3, rx: 1, fill: F2 })
7546
+ ] })
7547
+ },
7548
+ {
7549
+ name: "hub",
7550
+ label: "Hub",
7551
+ description: "Arranges ideas around the one thing they share, each joined to it by a spoke.",
7552
+ icon: /* @__PURE__ */ jsxs14(TemplateIcon, { children: [
7553
+ [
7554
+ [10, 9],
7555
+ [10, 33],
7556
+ [46, 9],
7557
+ [46, 33]
7558
+ ].map(([x, y]) => /* @__PURE__ */ jsxs14("g", { children: [
7559
+ /* @__PURE__ */ jsx19("line", { x1: 28, y1: 21, x2: x, y2: y, stroke: FA, strokeWidth: 1.5, opacity: 0.6 }),
7560
+ /* @__PURE__ */ jsx19("circle", { cx: x, cy: y, r: 3, fill: FA })
7561
+ ] }, `${x}-${y}`)),
7562
+ /* @__PURE__ */ jsx19("circle", { cx: 28, cy: 21, r: 7, fill: FA })
7563
+ ] })
7564
+ },
7565
+ {
7566
+ name: "venn",
7567
+ label: "Venn",
7568
+ description: "Draws two sets as overlapping circles, with what they share in the overlap.",
7569
+ icon: /* @__PURE__ */ jsxs14(TemplateIcon, { children: [
7570
+ /* @__PURE__ */ jsx19("circle", { cx: 21, cy: 19, r: 12, fill: FA, opacity: 0.5 }),
7571
+ /* @__PURE__ */ jsx19("circle", { cx: 35, cy: 19, r: 12, fill: F2, opacity: 0.6 }),
7572
+ /* @__PURE__ */ jsx19("rect", { x: 20, y: 36, width: 16, height: 3, rx: 1, fill: F2 })
7573
+ ] })
7574
+ },
7575
+ {
7576
+ name: "matrix",
7577
+ label: "Matrix",
7578
+ description: "Places four ideas on two axes as a two-by-two grid.",
7579
+ icon: /* @__PURE__ */ jsxs14(TemplateIcon, { children: [
7580
+ /* @__PURE__ */ jsx19("line", { x1: 5, y1: 36, x2: 5, y2: 5, stroke: F2, strokeWidth: 1.5 }),
7581
+ /* @__PURE__ */ jsx19("line", { x1: 5, y1: 36, x2: 52, y2: 36, stroke: F2, strokeWidth: 1.5 }),
7582
+ [
7583
+ [9, 5],
7584
+ [31, 5],
7585
+ [9, 20],
7586
+ [31, 20]
7587
+ ].map(([x, y]) => /* @__PURE__ */ jsxs14("g", { children: [
7588
+ /* @__PURE__ */ jsx19("rect", { x, y, width: 20, height: 13, rx: 2, fill: F2, opacity: 0.35 }),
7589
+ /* @__PURE__ */ jsx19("rect", { x: x + 2, y: y + 3, width: 6, height: 2, rx: 1, fill: FA })
7590
+ ] }, `${x}-${y}`))
7591
+ ] })
7592
+ },
7247
7593
  {
7248
7594
  name: "photoGrid",
7249
7595
  label: "Photo Grid",
@@ -7590,7 +7936,7 @@ function TemplatePicker({
7590
7936
  updateDialogBounds();
7591
7937
  setOpen((v2) => !v2);
7592
7938
  };
7593
- useEffect14(() => {
7939
+ useEffect15(() => {
7594
7940
  if (!open) return;
7595
7941
  const handler = (e2) => {
7596
7942
  const target = e2.target;
@@ -7603,7 +7949,7 @@ function TemplatePicker({
7603
7949
  document.addEventListener("mousedown", handler);
7604
7950
  return () => document.removeEventListener("mousedown", handler);
7605
7951
  }, [open]);
7606
- useEffect14(() => {
7952
+ useEffect15(() => {
7607
7953
  if (!open) return;
7608
7954
  requestAnimationFrame(updateDialogBounds);
7609
7955
  const handler = () => updateDialogBounds();
@@ -7825,7 +8171,7 @@ function TemplateGalleryBody({
7825
8171
  const searchRef = useRef13(null);
7826
8172
  const customCtx = useCustomTemplates();
7827
8173
  const customTemplates = useMemo11(() => customCtx?.allTemplates ?? [], [customCtx]);
7828
- useEffect14(() => {
8174
+ useEffect15(() => {
7829
8175
  searchRef.current?.focus();
7830
8176
  }, []);
7831
8177
  const trimmedQuery = query.trim().toLowerCase();
@@ -8046,11 +8392,11 @@ function TemplateBadgePopover({
8046
8392
  const [style, setStyle] = useState16(() => computeDialogStyle(anchorRect));
8047
8393
  const dialogRef = useRef13(null);
8048
8394
  const dialogId = `${TEMPLATE_GALLERY_DIALOG_ID}-${useId3().replace(/:/g, "")}`;
8049
- useEffect14(() => {
8395
+ useEffect15(() => {
8050
8396
  const frame = requestAnimationFrame(() => setStyle(computeDialogStyle(anchorRect)));
8051
8397
  return () => cancelAnimationFrame(frame);
8052
8398
  }, [anchorRect]);
8053
- useEffect14(() => {
8399
+ useEffect15(() => {
8054
8400
  const onKey = (e2) => {
8055
8401
  if (e2.key === "Escape") onClose();
8056
8402
  };
@@ -8384,7 +8730,7 @@ function transitionLabel(value) {
8384
8730
  }
8385
8731
 
8386
8732
  // src/TransitionPicker.tsx
8387
- import { useEffect as useEffect15, useMemo as useMemo12, useRef as useRef14, useState as useState17 } from "react";
8733
+ import { useEffect as useEffect16, useMemo as useMemo12, useRef as useRef14, useState as useState17 } from "react";
8388
8734
  import { createPortal as createPortal8 } from "react-dom";
8389
8735
  import { getTransitionClass } from "@bendyline/squisq-react";
8390
8736
  import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
@@ -8423,7 +8769,7 @@ function TransitionPicker({
8423
8769
  updatePosition();
8424
8770
  setOpen((v2) => !v2);
8425
8771
  };
8426
- useEffect15(() => {
8772
+ useEffect16(() => {
8427
8773
  if (!open) return;
8428
8774
  const handler = (e2) => {
8429
8775
  const target = e2.target;
@@ -8434,7 +8780,7 @@ function TransitionPicker({
8434
8780
  document.addEventListener("mousedown", handler);
8435
8781
  return () => document.removeEventListener("mousedown", handler);
8436
8782
  }, [open]);
8437
- useEffect15(() => {
8783
+ useEffect16(() => {
8438
8784
  if (!open) return;
8439
8785
  requestAnimationFrame(updatePosition);
8440
8786
  const handler = () => updatePosition();
@@ -8503,7 +8849,7 @@ function TransitionFlyout({
8503
8849
  }) {
8504
8850
  const [query, setQuery] = useState17("");
8505
8851
  const searchRef = useRef14(null);
8506
- useEffect15(() => {
8852
+ useEffect16(() => {
8507
8853
  searchRef.current?.focus();
8508
8854
  }, []);
8509
8855
  const trimmed = query.trim().toLowerCase();
@@ -8660,7 +9006,7 @@ function normalizeDir(direction) {
8660
9006
  }
8661
9007
 
8662
9008
  // src/hooks/useFileDrop.ts
8663
- import { useCallback as useCallback13, useEffect as useEffect16, useRef as useRef15, useState as useState18 } from "react";
9009
+ import { useCallback as useCallback13, useEffect as useEffect17, useRef as useRef15, useState as useState18 } from "react";
8664
9010
  var MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
8665
9011
  "png",
8666
9012
  "apng",
@@ -8746,7 +9092,7 @@ function useFileDrop({ onDrop, enabled = true }) {
8746
9092
  const [dragContentType, setDragContentType] = useState18(null);
8747
9093
  const dragCounterRef = useRef15(0);
8748
9094
  const inPageDragRef = useRef15(false);
8749
- useEffect16(() => {
9095
+ useEffect17(() => {
8750
9096
  if (!enabled) return;
8751
9097
  const onStart = () => {
8752
9098
  inPageDragRef.current = true;
@@ -9092,7 +9438,7 @@ import {
9092
9438
  createContext as createContext5,
9093
9439
  useCallback as useCallback14,
9094
9440
  useContext as useContext5,
9095
- useEffect as useEffect17,
9441
+ useEffect as useEffect18,
9096
9442
  useMemo as useMemo13,
9097
9443
  useRef as useRef16,
9098
9444
  useState as useState19
@@ -9915,7 +10261,7 @@ function useEditorContextMenuItems(items) {
9915
10261
  if (!registry) {
9916
10262
  throw new Error("useEditorContextMenuItems must be used within an <EditorShell>");
9917
10263
  }
9918
- useEffect17(() => registry.register(() => itemsRef.current), [registry]);
10264
+ useEffect18(() => registry.register(() => itemsRef.current), [registry]);
9919
10265
  }
9920
10266
  function selectedTextFromBrowser(content) {
9921
10267
  const selection = window.getSelection();
@@ -10337,7 +10683,7 @@ function EditorContextMenuProvider({
10337
10683
  },
10338
10684
  [buildInteraction, tiptapEditor]
10339
10685
  );
10340
- useEffect17(() => {
10686
+ useEffect18(() => {
10341
10687
  const root = rootRef.current;
10342
10688
  if (!root) return;
10343
10689
  const onContextMenu = (event) => {
@@ -10362,7 +10708,7 @@ function EditorContextMenuProvider({
10362
10708
  root.removeEventListener("keydown", onKeyDown);
10363
10709
  };
10364
10710
  }, [openAt, rootRef]);
10365
- useEffect17(() => {
10711
+ useEffect18(() => {
10366
10712
  if (!menu) return;
10367
10713
  const frame = requestAnimationFrame(() => {
10368
10714
  setMenuStyle(computeMenuStyle(menu, menuRef.current));
@@ -10469,7 +10815,7 @@ function EditorContextMenuProvider({
10469
10815
  }
10470
10816
 
10471
10817
  // src/DocumentSettingsDialog.tsx
10472
- import { useCallback as useCallback15, useEffect as useEffect18, useId as useId4, useMemo as useMemo14, useRef as useRef17, useState as useState20 } from "react";
10818
+ import { useCallback as useCallback15, useEffect as useEffect19, useId as useId4, useMemo as useMemo14, useRef as useRef17, useState as useState20 } from "react";
10473
10819
  import {
10474
10820
  inferDocumentTitle,
10475
10821
  parseMarkdown as parseMarkdown3,
@@ -10567,7 +10913,7 @@ function DocumentSettingsDialog({
10567
10913
  const currentPageTemplate = readPageTemplate(frontmatter);
10568
10914
  const [pageTemplate, setPageTemplate] = useState20(currentPageTemplate);
10569
10915
  const [templateChoices, setTemplateChoices] = useState20({ status: "loading", choices: [] });
10570
- useEffect18(() => {
10916
+ useEffect19(() => {
10571
10917
  if (!pageTemplates) return;
10572
10918
  let current = true;
10573
10919
  pageTemplates.listTemplates().then(
@@ -12117,7 +12463,7 @@ function searchPickerEntries(query, limit = 80) {
12117
12463
  }
12118
12464
 
12119
12465
  // src/EmojiPicker.tsx
12120
- import { useEffect as useEffect19, useMemo as useMemo15, useRef as useRef18, useState as useState21 } from "react";
12466
+ import { useEffect as useEffect20, useMemo as useMemo15, useRef as useRef18, useState as useState21 } from "react";
12121
12467
  import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
12122
12468
  var LIGHT_PALETTE = {
12123
12469
  bg: "#fff",
@@ -12174,7 +12520,7 @@ function EmojiPicker({
12174
12520
  const popoverRef = useRef18(null);
12175
12521
  useEscapeDismissal(open, onClose, anchorRef);
12176
12522
  const searchInputRef = useRef18(null);
12177
- useEffect19(() => {
12523
+ useEffect20(() => {
12178
12524
  if (open) {
12179
12525
  const t = setTimeout(() => searchInputRef.current?.focus(), 0);
12180
12526
  return () => clearTimeout(t);
@@ -12183,7 +12529,7 @@ function EmojiPicker({
12183
12529
  setActiveCategory(PICKER_CATEGORIES[0].id);
12184
12530
  return void 0;
12185
12531
  }, [open]);
12186
- useEffect19(() => {
12532
+ useEffect20(() => {
12187
12533
  if (!open) return;
12188
12534
  const onPointerDown = (e2) => {
12189
12535
  const target = e2.target;
@@ -12298,7 +12644,7 @@ function EmojiPicker({
12298
12644
  minHeight: 0,
12299
12645
  overflowY: "auto",
12300
12646
  display: "grid",
12301
- gridTemplateColumns: "repeat(10, 1fr)",
12647
+ gridTemplateColumns: "repeat(auto-fill, minmax(max(36px, calc((100% - 18px) / 10)), 1fr))",
12302
12648
  gap: 2
12303
12649
  },
12304
12650
  children: visibleEntries.length === 0 ? /* @__PURE__ */ jsx24(
@@ -12591,7 +12937,7 @@ async function addFileToDocument(file, target, storage) {
12591
12937
  }
12592
12938
 
12593
12939
  // src/videoEmbed/VideoEmbedDialog.tsx
12594
- import { useCallback as useCallback17, useEffect as useEffect20, useId as useId5, useMemo as useMemo17, useRef as useRef19, useState as useState22 } from "react";
12940
+ import { useCallback as useCallback17, useEffect as useEffect21, useId as useId5, useMemo as useMemo17, useRef as useRef19, useState as useState22 } from "react";
12595
12941
  import {
12596
12942
  VIDEO_EMBED_PROVIDERS,
12597
12943
  parseVideoEmbedInput as parseVideoEmbedInput2
@@ -12635,7 +12981,7 @@ function VideoEmbedDialog({
12635
12981
  useModalDialog({ rootRef: overlayRef, dialogRef, initialFocusRef: inputRef, onClose });
12636
12982
  const parsed = useMemo17(() => parseVideoEmbedInput2(input), [input]);
12637
12983
  const hasInput = input.trim() !== "";
12638
- useEffect20(() => {
12984
+ useEffect21(() => {
12639
12985
  if (!titleTouched.current && parsed?.title) setTitle(parsed.title);
12640
12986
  }, [parsed]);
12641
12987
  const handleSubmit = useCallback17(
@@ -13590,8 +13936,8 @@ function MermaidDiagramTypeThumbnail({ preview }) {
13590
13936
  // src/Toolbar.tsx
13591
13937
  import {
13592
13938
  useCallback as useCallback32,
13593
- useEffect as useEffect32,
13594
- useLayoutEffect as useLayoutEffect9,
13939
+ useEffect as useEffect33,
13940
+ useLayoutEffect as useLayoutEffect10,
13595
13941
  useMemo as useMemo25,
13596
13942
  useReducer as useReducer2,
13597
13943
  useRef as useRef37,
@@ -13979,7 +14325,7 @@ ${videoTag}` : videoTag);
13979
14325
  }
13980
14326
 
13981
14327
  // src/speech/DictationButton.tsx
13982
- import { useEffect as useEffect21, useRef as useRef20, useState as useState23, useSyncExternalStore } from "react";
14328
+ import { useEffect as useEffect22, useRef as useRef20, useState as useState23, useSyncExternalStore } from "react";
13983
14329
  import { jsx as jsx29, jsxs as jsxs21 } from "react/jsx-runtime";
13984
14330
  var ERROR_AUTO_DISMISS_MS = 1e4;
13985
14331
  var ANNOUNCEMENTS = {
@@ -14033,12 +14379,12 @@ function DictationButtonView({ state }) {
14033
14379
  const busy = status === "preparing" || status === "transcribing";
14034
14380
  const [announcement, setAnnouncement] = useState23("");
14035
14381
  const previousStatus = useRef20(status);
14036
- useEffect21(() => {
14382
+ useEffect22(() => {
14037
14383
  if (previousStatus.current === status) return;
14038
14384
  previousStatus.current = status;
14039
14385
  setAnnouncement(ANNOUNCEMENTS[status]);
14040
14386
  }, [status]);
14041
- useEffect21(() => {
14387
+ useEffect22(() => {
14042
14388
  if (!error) return;
14043
14389
  const timer = setTimeout(dismissError, ERROR_AUTO_DISMISS_MS);
14044
14390
  return () => clearTimeout(timer);
@@ -14139,7 +14485,7 @@ function findBlockSliceByHeadingIndexInDoc(doc, headingIndex) {
14139
14485
  }
14140
14486
 
14141
14487
  // src/LinkDialog.tsx
14142
- import { useCallback as useCallback19, useEffect as useEffect22, useId as useId6, useMemo as useMemo19, useRef as useRef21, useState as useState24 } from "react";
14488
+ import { useCallback as useCallback19, useEffect as useEffect23, useId as useId6, useMemo as useMemo19, useRef as useRef21, useState as useState24 } from "react";
14143
14489
  import { sanitizeUrl, wikiLinkPath, parseWikiLink as parseWikiLink2 } from "@bendyline/squisq/markdown";
14144
14490
  import { jsx as jsx30, jsxs as jsxs22 } from "react/jsx-runtime";
14145
14491
  function schemeForMessage(raw) {
@@ -14174,7 +14520,7 @@ function LinkDialog({
14174
14520
  const urlLabelId = useId6();
14175
14521
  const initialFocusRef = kind === "wiki" ? textRef : initialText ? urlRef : textRef;
14176
14522
  useModalDialog({ rootRef: overlayRef, dialogRef, initialFocusRef, onClose });
14177
- useEffect22(() => {
14523
+ useEffect23(() => {
14178
14524
  if (!documentLinkProvider || tab !== "documents") return;
14179
14525
  let cancelled = false;
14180
14526
  setDocLoading(true);
@@ -14193,7 +14539,7 @@ function LinkDialog({
14193
14539
  cancelled = true;
14194
14540
  };
14195
14541
  }, [docQuery, documentLinkProvider, tab]);
14196
- useEffect22(() => {
14542
+ useEffect23(() => {
14197
14543
  if (tab === "documents") {
14198
14544
  const t = setTimeout(() => docSearchRef.current?.focus(), 0);
14199
14545
  return () => clearTimeout(t);
@@ -14457,8 +14803,8 @@ import { MediaContext as MediaContext3 } from "@bendyline/squisq-react";
14457
14803
  // src/scene/Scene.tsx
14458
14804
  import {
14459
14805
  useCallback as useCallback25,
14460
- useEffect as useEffect24,
14461
- useLayoutEffect as useLayoutEffect7,
14806
+ useEffect as useEffect25,
14807
+ useLayoutEffect as useLayoutEffect8,
14462
14808
  useMemo as useMemo21,
14463
14809
  useRef as useRef26,
14464
14810
  useState as useState28
@@ -14575,11 +14921,11 @@ function useSceneSelection(initial = []) {
14575
14921
  return next;
14576
14922
  });
14577
14923
  }, []);
14578
- const clear = useCallback21(() => {
14924
+ const clear2 = useCallback21(() => {
14579
14925
  setSelectionState((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Set());
14580
14926
  }, []);
14581
14927
  const isSelected = useCallback21((id) => selection.has(id), [selection]);
14582
- return { selection, setSelection, select, toggle, clear, isSelected };
14928
+ return { selection, setSelection, select, toggle, clear: clear2, isSelected };
14583
14929
  }
14584
14930
 
14585
14931
  // src/scene/hooks/useSceneHitTest.ts
@@ -14975,11 +15321,48 @@ function useSceneTextEditing(config3) {
14975
15321
  }
14976
15322
 
14977
15323
  // src/scene/text/SceneTextOverlay.tsx
14978
- import { useCallback as useCallback24, useEffect as useEffect23, useMemo as useMemo20, useRef as useRef25 } from "react";
15324
+ import { useCallback as useCallback24, useEffect as useEffect24, useMemo as useMemo20, useRef as useRef25 } from "react";
14979
15325
  import { useEditor, EditorContent } from "@tiptap/react";
14980
15326
  import { DEFAULT_DOC_FONT } from "@bendyline/squisq/schemas";
14981
15327
  import { plainTextFromInlineHtml } from "@bendyline/squisq/markdown";
14982
15328
 
15329
+ // src/scene/text/sceneTextFrame.ts
15330
+ var TOUCH_MIN_EDIT_FONT_PX = 16;
15331
+ function sceneTextFrame({
15332
+ origin,
15333
+ size,
15334
+ zoom,
15335
+ fontSize,
15336
+ minFontPx,
15337
+ container
15338
+ }) {
15339
+ const scale = fontSize > 0 && minFontPx > 0 ? Math.max(zoom, minFontPx / fontSize) : zoom;
15340
+ if (scale === zoom) {
15341
+ return {
15342
+ left: origin.x,
15343
+ top: origin.y,
15344
+ width: size.width * zoom,
15345
+ height: size.height * zoom,
15346
+ scale
15347
+ };
15348
+ }
15349
+ let width = size.width * scale;
15350
+ let height = size.height * scale;
15351
+ let left = origin.x + (size.width * zoom - width) / 2;
15352
+ let top = origin.y + (size.height * zoom - height) / 2;
15353
+ if (container && container.width > 0 && container.height > 0) {
15354
+ width = Math.min(width, container.width);
15355
+ height = Math.min(height, container.height);
15356
+ left = Math.min(Math.max(left, 0), container.width - width);
15357
+ top = Math.min(Math.max(top, 0), container.height - height);
15358
+ }
15359
+ return { left, top, width, height, scale };
15360
+ }
15361
+ function editFontFloorPx() {
15362
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return 0;
15363
+ return window.matchMedia("(pointer: coarse)").matches ? TOUCH_MIN_EDIT_FONT_PX : 0;
15364
+ }
15365
+
14983
15366
  // src/scene/text/sceneTiptap.ts
14984
15367
  import StarterKit from "@tiptap/starter-kit";
14985
15368
  import Link from "@tiptap/extension-link";
@@ -15004,10 +15387,13 @@ function SceneTextOverlay({
15004
15387
  viewport,
15005
15388
  transform,
15006
15389
  viewportToScreen,
15390
+ container,
15007
15391
  onClose
15008
15392
  }) {
15009
15393
  const initialHtml = useMemo20(() => config3.getHtml(editableId), [config3, editableId]);
15010
- const style = layer.type === "text" ? layer.content.style : void 0;
15394
+ const style = layer.type === "text" ? layer.content.style : config3.textStyle?.(editableId);
15395
+ const textColor = layer.type === "text" ? layer.content.style.color : void 0;
15396
+ const fontFloorPx = useMemo20(editFontFloorPx, []);
15011
15397
  const committedRef = useRef25(false);
15012
15398
  const commitRef = useRef25(() => {
15013
15399
  });
@@ -15045,7 +15431,7 @@ function SceneTextOverlay({
15045
15431
  committedRef.current = true;
15046
15432
  onClose();
15047
15433
  }, [onClose]);
15048
- useEffect23(() => {
15434
+ useEffect24(() => {
15049
15435
  if (!editor || !config3.channel) return;
15050
15436
  const channel = config3.channel;
15051
15437
  channel.set({ editor, level: config3.level });
@@ -15060,10 +15446,17 @@ function SceneTextOverlay({
15060
15446
  }, []);
15061
15447
  const bounds = layerBounds(layer, viewport);
15062
15448
  if (!bounds) return null;
15063
- const tl = viewportToScreen(bounds.x, bounds.y);
15064
- const scale = transform.scale;
15065
- const fontSize = (style?.fontSize ?? 18) * scale;
15066
- const pad = (style?.padding ?? 0) * scale;
15449
+ const baseFontSize = style?.fontSize ?? 18;
15450
+ const frame = sceneTextFrame({
15451
+ origin: viewportToScreen(bounds.x, bounds.y),
15452
+ size: bounds,
15453
+ zoom: transform.scale,
15454
+ fontSize: baseFontSize,
15455
+ minFontPx: fontFloorPx,
15456
+ container
15457
+ });
15458
+ const fontSize = baseFontSize * frame.scale;
15459
+ const pad = (style?.padding ?? 0) * frame.scale;
15067
15460
  const justifyContent = style?.verticalAlign === "middle" ? "center" : style?.verticalAlign === "bottom" ? "flex-end" : "flex-start";
15068
15461
  return /* @__PURE__ */ jsx33(
15069
15462
  "div",
@@ -15075,10 +15468,10 @@ function SceneTextOverlay({
15075
15468
  onDoubleClick: (e2) => e2.stopPropagation(),
15076
15469
  style: {
15077
15470
  position: "absolute",
15078
- left: tl.x,
15079
- top: tl.y,
15080
- width: bounds.width * scale,
15081
- height: bounds.height * scale,
15471
+ left: frame.left,
15472
+ top: frame.top,
15473
+ width: frame.width,
15474
+ height: frame.height,
15082
15475
  zIndex: 20,
15083
15476
  boxSizing: "border-box",
15084
15477
  display: "flex",
@@ -15087,7 +15480,9 @@ function SceneTextOverlay({
15087
15480
  padding: pad,
15088
15481
  fontSize,
15089
15482
  fontFamily: style?.fontFamily || DEFAULT_DOC_FONT,
15090
- color: style?.color ?? "var(--squisq-text)",
15483
+ fontWeight: style?.fontWeight,
15484
+ fontStyle: style?.fontStyle,
15485
+ color: textColor ?? "var(--squisq-text)",
15091
15486
  lineHeight: style?.lineHeight || 1.4,
15092
15487
  textAlign: style?.textAlign ?? "left",
15093
15488
  background: "var(--squisq-surface)",
@@ -15307,7 +15702,7 @@ function Scene(props) {
15307
15702
  },
15308
15703
  [interaction, bumpTick]
15309
15704
  );
15310
- useEffect24(
15705
+ useEffect25(
15311
15706
  () => () => {
15312
15707
  interaction.selectDrag = void 0;
15313
15708
  interaction.connect = void 0;
@@ -15339,7 +15734,7 @@ function Scene(props) {
15339
15734
  },
15340
15735
  [onDrop, panZoom]
15341
15736
  );
15342
- useEffect24(() => {
15737
+ useEffect25(() => {
15343
15738
  const root = containerRef.current;
15344
15739
  if (!root) return;
15345
15740
  const onKey = (e2) => {
@@ -15362,7 +15757,7 @@ function Scene(props) {
15362
15757
  const [containerSize, setContainerSize] = useState28(
15363
15758
  null
15364
15759
  );
15365
- useLayoutEffect7(() => {
15760
+ useLayoutEffect8(() => {
15366
15761
  if (!containerRef.current) return;
15367
15762
  const el = containerRef.current;
15368
15763
  const measure = () => {
@@ -15393,7 +15788,7 @@ function Scene(props) {
15393
15788
  if (!containerSize || !contentBox) return;
15394
15789
  fitBox(contentBox, containerSize, 40, 1);
15395
15790
  }, [containerSize, contentBox, fitBox]);
15396
- useEffect24(() => {
15791
+ useEffect25(() => {
15397
15792
  if (viewMode === "fit") fitContent();
15398
15793
  }, [fitContent, viewMode]);
15399
15794
  const adjustZoom = useCallback25(
@@ -15543,6 +15938,7 @@ function Scene(props) {
15543
15938
  viewport,
15544
15939
  transform: panZoom.transform,
15545
15940
  viewportToScreen: panZoom.viewportToScreen,
15941
+ container: containerSize,
15546
15942
  onClose: textEdit.close
15547
15943
  }
15548
15944
  );
@@ -15624,10 +16020,10 @@ import { createElement as createElement7 } from "react";
15624
16020
  import { parsePandocAttrTokens as parsePandocAttrTokens6 } from "@bendyline/squisq/markdown";
15625
16021
 
15626
16022
  // src/scene/SceneBlockWidget.tsx
15627
- import { useEffect as useEffect28, useMemo as useMemo23, useRef as useRef30, useState as useState32 } from "react";
16023
+ import { useEffect as useEffect29, useMemo as useMemo23, useRef as useRef30, useState as useState32 } from "react";
15628
16024
 
15629
16025
  // src/scene/adapters/LayoutAdapter.ts
15630
- import { useEffect as useEffect25, useRef as useRef27, useState as useState29 } from "react";
16026
+ import { useEffect as useEffect26, useRef as useRef27, useState as useState29 } from "react";
15631
16027
  import { computeLayoutLayers } from "@bendyline/squisq/doc";
15632
16028
 
15633
16029
  // src/scene/adapters/blockLayers.ts
@@ -15975,7 +16371,7 @@ function stripBraces2(s) {
15975
16371
  var SCENE_VIEWPORT = { width: 1920, height: 1080 };
15976
16372
  function useLayoutAdapter(editor, headingPos, options = {}) {
15977
16373
  const [, setVersion] = useState29(0);
15978
- useEffect25(() => {
16374
+ useEffect26(() => {
15979
16375
  const onUpdate = () => setVersion((v2) => v2 + 1);
15980
16376
  editor.on("transaction", onUpdate);
15981
16377
  return () => {
@@ -16001,7 +16397,7 @@ function useLayoutAdapter(editor, headingPos, options = {}) {
16001
16397
  if (c.bodyHtml.trim()) l2.content.html = c.bodyHtml;
16002
16398
  }
16003
16399
  const migratedRef = useRef27(false);
16004
- useEffect25(() => {
16400
+ useEffect26(() => {
16005
16401
  if (migratedRef.current) return;
16006
16402
  const n = nodeAtOrNull(editor.state.doc, headingPos);
16007
16403
  if (!n) return;
@@ -16181,7 +16577,7 @@ function nextLayoutId(editor, parentPos, kind) {
16181
16577
  }
16182
16578
 
16183
16579
  // src/scene/adapters/DrawingAdapter.ts
16184
- import { useCallback as useCallback26, useEffect as useEffect26, useMemo as useMemo22, useRef as useRef28, useState as useState30, createElement as createElement6 } from "react";
16580
+ import { useCallback as useCallback26, useEffect as useEffect27, useMemo as useMemo22, useRef as useRef28, useState as useState30, createElement as createElement6 } from "react";
16185
16581
  import { computeDrawingLayout } from "@bendyline/squisq/doc";
16186
16582
 
16187
16583
  // src/scene/tools/PathTool.ts
@@ -16566,6 +16962,10 @@ function nodeCardLayers(node2) {
16566
16962
  };
16567
16963
  return [card, label];
16568
16964
  }
16965
+ function labelLayerIdForCard(cardId) {
16966
+ if (!cardId.startsWith("node-card-")) return cardId;
16967
+ return `node-label-${cardId.slice("node-card-".length)}`;
16968
+ }
16569
16969
  function cardLayerIdForLabel(labelId) {
16570
16970
  if (!labelId.startsWith("node-label-")) return labelId;
16571
16971
  return `node-card-${labelId.slice("node-label-".length)}`;
@@ -17102,7 +17502,7 @@ function headingText(node2) {
17102
17502
  var ENDPOINT_HIT_RADIUS = 18;
17103
17503
  function useDrawingAdapter(editor, headingPos, options = {}) {
17104
17504
  const [, setVersion] = useState30(0);
17105
- useEffect26(() => {
17505
+ useEffect27(() => {
17106
17506
  const onUpdate = () => setVersion((v2) => v2 + 1);
17107
17507
  editor.on("transaction", onUpdate);
17108
17508
  return () => {
@@ -17140,12 +17540,12 @@ function useDrawingAdapter(editor, headingPos, options = {}) {
17140
17540
  }, []);
17141
17541
  const dataRef = useRef28({ edges, boxes, selectedEdgeId, headingPos });
17142
17542
  dataRef.current = { edges, boxes, selectedEdgeId, headingPos };
17143
- useEffect26(() => {
17543
+ useEffect27(() => {
17144
17544
  if (selectedEdgeId && !edges.some((e2) => e2.id === selectedEdgeId)) {
17145
17545
  setSelectedEdgeId(null);
17146
17546
  }
17147
17547
  }, [edges, selectedEdgeId]);
17148
- useEffect26(() => {
17548
+ useEffect27(() => {
17149
17549
  const onKey = (e2) => {
17150
17550
  if (e2.key !== "Delete" && e2.key !== "Backspace") return;
17151
17551
  if (isEditableTarget(e2.target)) return;
@@ -17543,13 +17943,13 @@ function SceneBlockToolbar({
17543
17943
  }
17544
17944
 
17545
17945
  // src/scene/SceneSideToolbar.tsx
17546
- import { useLayoutEffect as useLayoutEffect8, useRef as useRef29, useState as useState31 } from "react";
17946
+ import { useLayoutEffect as useLayoutEffect9, useRef as useRef29, useState as useState31 } from "react";
17547
17947
  import { jsx as jsx39 } from "react/jsx-runtime";
17548
17948
  var SIDE_TOOLBAR_FIT_PX = 12 + 180 + 4;
17549
17949
  function SceneSideToolbar({ children }) {
17550
17950
  const wrapRef = useRef29(null);
17551
17951
  const [collapsed, setCollapsed] = useState31(false);
17552
- useLayoutEffect8(() => {
17952
+ useLayoutEffect9(() => {
17553
17953
  const wrap = wrapRef.current;
17554
17954
  if (!wrap) return;
17555
17955
  const shell = wrap.closest(".squisq-scene-shell");
@@ -17594,11 +17994,11 @@ function SceneSideToolbar({ children }) {
17594
17994
  }
17595
17995
 
17596
17996
  // src/diagram/DiagramMaximizedOverlay.tsx
17597
- import { useEffect as useEffect27 } from "react";
17997
+ import { useEffect as useEffect28 } from "react";
17598
17998
  import { createPortal as createPortal10 } from "react-dom";
17599
17999
  import { jsx as jsx40 } from "react/jsx-runtime";
17600
18000
  function DiagramMaximizedOverlay({ host, onClose, children }) {
17601
- useEffect27(() => {
18001
+ useEffect28(() => {
17602
18002
  const onKey = (e2) => {
17603
18003
  if (e2.key === "Escape") {
17604
18004
  e2.preventDefault();
@@ -17637,7 +18037,7 @@ function SceneBlockWidget(props) {
17637
18037
  const [parentPos, setParentPos] = useState32(
17638
18038
  () => resolveSceneHeadingPos(editor, headingKey, mode, fallbackParentPos)
17639
18039
  );
17640
- useEffect28(() => {
18040
+ useEffect29(() => {
17641
18041
  const onUpdate = () => setParentPos(
17642
18042
  (current) => resolveSceneHeadingPos(editor, headingKey, mode, current ?? fallbackParentPos)
17643
18043
  );
@@ -19560,7 +19960,7 @@ function ManagerContent({
19560
19960
  import { createPortal as createPortal13 } from "react-dom";
19561
19961
 
19562
19962
  // src/toolbar/EditorViewTabs.tsx
19563
- import { useEffect as useEffect29, useReducer, useRef as useRef34, useState as useState36 } from "react";
19963
+ import { useEffect as useEffect30, useReducer, useRef as useRef34, useState as useState36 } from "react";
19564
19964
 
19565
19965
  // src/platformShortcuts.ts
19566
19966
  function resolvePlatform(explicitPlatform) {
@@ -19590,7 +19990,7 @@ function navigateViewTabs(event) {
19590
19990
  import { Fragment as Fragment14, jsx as jsx47, jsxs as jsxs35 } from "react/jsx-runtime";
19591
19991
  var VIEWS = [
19592
19992
  { id: "wysiwyg", label: "Write", shortcutKey: "1" },
19593
- { id: "raw", label: "Source", shortcutKey: "2" },
19993
+ { id: "raw", label: "Source", icon: "fa-solid fa-code", shortcutKey: "2" },
19594
19994
  { id: "preview", label: "Use", shortcutKey: "3" }
19595
19995
  ];
19596
19996
  function EditorViewTabs({
@@ -19599,7 +19999,7 @@ function EditorViewTabs({
19599
19999
  }) {
19600
20000
  const { editorMode, activeView, setActiveView, layoutMode } = useEditorContext();
19601
20001
  const [focusedView, setFocusedView] = useState36(activeView);
19602
- useEffect29(() => setFocusedView(activeView), [activeView]);
20002
+ useEffect30(() => setFocusedView(activeView), [activeView]);
19603
20003
  const previewSettings = usePreviewSettingsOptional();
19604
20004
  const [useModeMenuRequest, requestUseModeMenu] = useReducer((count) => count + 1, 0);
19605
20005
  const [writeModeMenuRequest, requestWriteModeMenu] = useReducer((count) => count + 1, 0);
@@ -19622,6 +20022,7 @@ function EditorViewTabs({
19622
20022
  /* @__PURE__ */ jsx47("div", { className: "squisq-toolbar-view-tab-list", role: "tablist", "aria-label": "Editor view", children: visibleViews.map((view) => {
19623
20023
  const modeMenu = modeMenuOf(view.id);
19624
20024
  const viewLabel = modeMenu === "use" && previewSettings ? displayModeLabel(previewSettings.activeDisplayMode) : modeMenu === "write" ? writeModeLabel(layoutMode) : view.label;
20025
+ const shortLabel = modeMenu === "use" && previewSettings ? displayModeShortLabel(previewSettings.activeDisplayMode) : view.shortLabel;
19625
20026
  const tab = /* @__PURE__ */ jsxs35(
19626
20027
  "button",
19627
20028
  {
@@ -19644,6 +20045,7 @@ function EditorViewTabs({
19644
20045
  setActiveView(view.id);
19645
20046
  },
19646
20047
  "data-tooltip": `${viewLabel} (${platformShortcut(`Shift+${view.shortcutKey}`)})`,
20048
+ "aria-label": viewLabel,
19647
20049
  children: [
19648
20050
  /* @__PURE__ */ jsx47(
19649
20051
  "span",
@@ -19653,12 +20055,12 @@ function EditorViewTabs({
19653
20055
  children: viewLabel
19654
20056
  }
19655
20057
  ),
19656
- view.shortLabel && view.shortLabel !== view.label && /* @__PURE__ */ jsx47(
20058
+ view.icon ? /* @__PURE__ */ jsx47("span", { className: "squisq-toolbar-view-tab-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx47(Icon, { icon: view.icon }) }) : shortLabel && shortLabel !== viewLabel && /* @__PURE__ */ jsx47(
19657
20059
  "span",
19658
20060
  {
19659
20061
  className: "squisq-toolbar-view-tab-label squisq-toolbar-view-tab-label--short",
19660
- "data-label": view.shortLabel,
19661
- children: view.shortLabel
20062
+ "data-label": shortLabel,
20063
+ children: shortLabel
19662
20064
  }
19663
20065
  )
19664
20066
  ]
@@ -19916,7 +20318,7 @@ function selectionToTaskListMarkdown(text) {
19916
20318
  }
19917
20319
 
19918
20320
  // src/find/FindToolbar.tsx
19919
- import { useCallback as useCallback31, useEffect as useEffect30, useId as useId11, useRef as useRef35, useState as useState38 } from "react";
20321
+ import { useCallback as useCallback31, useEffect as useEffect31, useId as useId11, useRef as useRef35, useState as useState38 } from "react";
19920
20322
 
19921
20323
  // src/find/FindHighlightExtension.ts
19922
20324
  import { Extension as Extension4 } from "@tiptap/core";
@@ -20101,15 +20503,15 @@ function FindToolbar({
20101
20503
  const options = { matchCase, wholeWord };
20102
20504
  const lastNavigationRef = useRef35("");
20103
20505
  const pendingRevealRef = useRef35(false);
20104
- useEffect30(() => {
20506
+ useEffect31(() => {
20105
20507
  inputRef.current?.focus();
20106
20508
  inputRef.current?.select();
20107
20509
  }, [focusRequest]);
20108
- useEffect30(() => {
20510
+ useEffect31(() => {
20109
20511
  if (showReplace) replaceInputRef.current?.focus();
20110
20512
  }, [showReplace, focusRequest]);
20111
20513
  const appliedRequestRef = useRef35(findRequest);
20112
- useEffect30(() => {
20514
+ useEffect31(() => {
20113
20515
  if (!findRequest || findRequest === appliedRequestRef.current) return;
20114
20516
  appliedRequestRef.current = findRequest;
20115
20517
  setQuery(findRequest.query);
@@ -20117,14 +20519,14 @@ function FindToolbar({
20117
20519
  setWholeWord(findRequest.wholeWord ?? false);
20118
20520
  setSelectedIndex(findRequest.occurrence ?? 0);
20119
20521
  }, [findRequest]);
20120
- useEffect30(() => {
20522
+ useEffect31(() => {
20121
20523
  const style = document.createElement("style");
20122
20524
  style.dataset.squisqFindHighlight = reactId;
20123
20525
  style.textContent = `::highlight(${previewHighlightName}) { background: var(--squisq-find-match, #fde68a); color: inherit; } ::highlight(${previewSelectedName}) { background: var(--squisq-find-match-current, #f59e0b); color: var(--squisq-find-match-text, #111827); }`;
20124
20526
  document.head.append(style);
20125
20527
  return () => style.remove();
20126
20528
  }, [previewHighlightName, previewSelectedName, reactId]);
20127
- useEffect30(() => {
20529
+ useEffect31(() => {
20128
20530
  let cleanup = () => {
20129
20531
  };
20130
20532
  let count = 0;
@@ -20547,7 +20949,7 @@ function markPreviewRanges(ranges, selectedIndex) {
20547
20949
  }
20548
20950
 
20549
20951
  // src/toolbar/useToolbarCollapse.ts
20550
- import { useEffect as useEffect31, useRef as useRef36, useState as useState39 } from "react";
20952
+ import { useEffect as useEffect32, useRef as useRef36, useState as useState39 } from "react";
20551
20953
 
20552
20954
  // src/toolbar/toolbarCollapse.ts
20553
20955
  var TOOLBAR_COLLAPSE_ORDER = [
@@ -20618,7 +21020,7 @@ function useToolbarCollapse(activeView, actionsLaneShown) {
20618
21020
  const itemWidthsRef = useRef36(/* @__PURE__ */ new Map());
20619
21021
  const chromeOnLaneRowRef = useRef36(/* @__PURE__ */ new Map());
20620
21022
  const collapsedCountRef = useRef36(0);
20621
- useEffect31(() => {
21023
+ useEffect32(() => {
20622
21024
  const container = actionsRef.current;
20623
21025
  if (!container) return;
20624
21026
  const toolbar = container.parentElement;
@@ -21243,7 +21645,7 @@ function Toolbar({
21243
21645
  const showDocumentChrome = !isCodeMode && hostMode !== "chat";
21244
21646
  const [scannedFileCount, setScannedFileCount] = useState40(0);
21245
21647
  const resolvedFileCount = fileCount ?? scannedFileCount;
21246
- useEffect32(() => {
21648
+ useEffect33(() => {
21247
21649
  if (fileCount !== void 0) return;
21248
21650
  if (!mediaProvider) {
21249
21651
  setScannedFileCount(0);
@@ -21371,7 +21773,7 @@ function Toolbar({
21371
21773
  const overflowRef = useRef37(null);
21372
21774
  const overflowMenuRef = useRef37(null);
21373
21775
  const overflowStyle = useAnchoredPopover(showOverflow, overflowTriggerRef, overflowMenuRef);
21374
- useLayoutEffect9(() => {
21776
+ useLayoutEffect10(() => {
21375
21777
  if (showOverflow && overflowStyle.visibility === "visible")
21376
21778
  overflowMenuRef.current?.querySelector("button:not(:disabled)")?.focus();
21377
21779
  }, [showOverflow, overflowStyle.visibility]);
@@ -21379,7 +21781,7 @@ function Toolbar({
21379
21781
  useEscapeDismissal(insertMenuAnchor !== null, closeInsertMenu, insertMenuReturnFocusRef);
21380
21782
  const [showDocSettings, setShowDocSettings] = useState40(false);
21381
21783
  const [showLayoutManager, setShowLayoutManager] = useState40(false);
21382
- useEffect32(() => {
21784
+ useEffect33(() => {
21383
21785
  if (!findMode) return;
21384
21786
  setShowOverflow(false);
21385
21787
  closeInsertMenu();
@@ -21388,10 +21790,10 @@ function Toolbar({
21388
21790
  const toolbarRootRef = useRef37(null);
21389
21791
  const [replaceOpen, setReplaceOpen] = useState40(false);
21390
21792
  const [findFocusRequest, setFindFocusRequest] = useState40(0);
21391
- useEffect32(() => {
21793
+ useEffect33(() => {
21392
21794
  if (!findMode) setReplaceOpen(false);
21393
21795
  }, [findMode]);
21394
- useEffect32(() => {
21796
+ useEffect33(() => {
21395
21797
  if (!findShortcut || editorMode === "image") return;
21396
21798
  const apple = isApplePlatform();
21397
21799
  const onKeyDown = (event) => {
@@ -21416,7 +21818,7 @@ function Toolbar({
21416
21818
  activeView,
21417
21819
  actionsLaneShown
21418
21820
  );
21419
- useEffect32(() => {
21821
+ useEffect33(() => {
21420
21822
  if (!showOverflow) return;
21421
21823
  const handleClick = (e2) => {
21422
21824
  const target = e2.target;
@@ -21430,7 +21832,7 @@ function Toolbar({
21430
21832
  document.addEventListener("mousedown", handleClick);
21431
21833
  return () => document.removeEventListener("mousedown", handleClick);
21432
21834
  }, [showOverflow]);
21433
- useEffect32(() => {
21835
+ useEffect33(() => {
21434
21836
  if (!insertMenuAnchor) return;
21435
21837
  const handleClick = (e2) => {
21436
21838
  if (insertMenuButtonRef.current?.contains(e2.target)) return;
@@ -21444,7 +21846,7 @@ function Toolbar({
21444
21846
  return () => document.removeEventListener("mousedown", handleClick);
21445
21847
  }, [insertMenuAnchor, closeInsertMenu]);
21446
21848
  const insertMenuOpen = insertMenuAnchor !== null;
21447
- useLayoutEffect9(() => {
21849
+ useLayoutEffect10(() => {
21448
21850
  if (!insertMenuOpen) return;
21449
21851
  const menu = insertMenuRef.current;
21450
21852
  const trigger = insertMenuTriggerRef.current;
@@ -21482,7 +21884,7 @@ function Toolbar({
21482
21884
  };
21483
21885
  }, [insertMenuOpen]);
21484
21886
  const [, forceUpdate] = useReducer2((c) => c + 1, 0);
21485
- useEffect32(() => {
21887
+ useEffect33(() => {
21486
21888
  if (!formattingEditor) return;
21487
21889
  formattingEditor.on("transaction", forceUpdate);
21488
21890
  return () => {
@@ -22123,7 +22525,7 @@ ${TASK_LIST_MARKDOWN}
22123
22525
  },
22124
22526
  [activeView, tiptapEditor, monacoEditor, markdownSource, setMarkdownSource, closeEmojiPicker]
22125
22527
  );
22126
- useEffect32(() => {
22528
+ useEffect33(() => {
22127
22529
  const onKeyDown = (e2) => {
22128
22530
  if (!(e2.ctrlKey || e2.metaKey) || e2.altKey || e2.shiftKey) return;
22129
22531
  if (e2.key.toLowerCase() !== "k") return;
@@ -22255,7 +22657,7 @@ ${TASK_LIST_MARKDOWN}
22255
22657
  ]);
22256
22658
  const BLOCK_SCENE_BUTTONS = /* @__PURE__ */ new Set([...INLINE_SCENE_BUTTONS, "ul", "ol", "quote", "codeblock"]);
22257
22659
  const [dataCardFocused, setDataCardFocused] = useState40(false);
22258
- useEffect32(() => {
22660
+ useEffect33(() => {
22259
22661
  let timer = null;
22260
22662
  const update = () => {
22261
22663
  if (timer !== null) clearTimeout(timer);
@@ -22318,7 +22720,7 @@ ${TASK_LIST_MARKDOWN}
22318
22720
  const [rawTemplate, setRawTemplate] = useState40(null);
22319
22721
  const [rawHeadingLine, setRawHeadingLine] = useState40(null);
22320
22722
  const [rawTransition, setRawTransition] = useState40(EMPTY_TRANSITION);
22321
- useEffect32(() => {
22723
+ useEffect33(() => {
22322
22724
  if (!isRawView || !monacoEditor) {
22323
22725
  setRawTemplate(null);
22324
22726
  setRawHeadingLine(null);
@@ -22368,7 +22770,7 @@ ${TASK_LIST_MARKDOWN}
22368
22770
  };
22369
22771
  }, [isRawView, monacoEditor]);
22370
22772
  const [wysiwygHeadingIndex, setWysiwygHeadingIndex] = useState40(null);
22371
- useEffect32(() => {
22773
+ useEffect33(() => {
22372
22774
  if (!isWysiwyg || !tiptapEditor) {
22373
22775
  setWysiwygHeadingIndex(null);
22374
22776
  return;
@@ -23514,7 +23916,7 @@ ${TASK_LIST_MARKDOWN}
23514
23916
  }
23515
23917
 
23516
23918
  // src/StatusBar.tsx
23517
- import { useEffect as useEffect33, useMemo as useMemo26, useState as useState41 } from "react";
23919
+ import { useEffect as useEffect34, useMemo as useMemo26, useState as useState41 } from "react";
23518
23920
  import { countBlocks } from "@bendyline/squisq/doc";
23519
23921
  import { jsx as jsx53, jsxs as jsxs40 } from "react/jsx-runtime";
23520
23922
  var STATS_DEBOUNCE_MS = 150;
@@ -23525,7 +23927,7 @@ function StatusBar({ className, slotRight }) {
23525
23927
  const { markdownSource, doc, parseError } = useEditorContext();
23526
23928
  const proofingState = useProofingState();
23527
23929
  const [statsSource, setStatsSource] = useState41(markdownSource);
23528
- useEffect33(() => {
23930
+ useEffect34(() => {
23529
23931
  if (statsSource === markdownSource) return;
23530
23932
  const timeout = setTimeout(() => setStatsSource(markdownSource), STATS_DEBOUNCE_MS);
23531
23933
  return () => clearTimeout(timeout);
@@ -23567,7 +23969,13 @@ function StatusBar({ className, slotRight }) {
23567
23969
  }
23568
23970
 
23569
23971
  // src/RawEditor.tsx
23570
- import { useRef as useRef38, useCallback as useCallback33, useEffect as useEffect34, useState as useState42 } from "react";
23972
+ import {
23973
+ useRef as useRef38,
23974
+ useCallback as useCallback33,
23975
+ useEffect as useEffect35,
23976
+ useLayoutEffect as useLayoutEffect11,
23977
+ useState as useState42
23978
+ } from "react";
23571
23979
 
23572
23980
  // src/wikiSuggestions.ts
23573
23981
  import { wikiLinkPath as wikiLinkPath2 } from "@bendyline/squisq/markdown";
@@ -23628,11 +24036,50 @@ var SQUISQ_THEMES = {
23628
24036
  vs: "squisq-light",
23629
24037
  "vs-dark": "squisq-dark"
23630
24038
  };
24039
+ var COMPACT_GUTTER_MAX_WIDTH = 520;
24040
+ var FULL_GUTTER = {
24041
+ lineNumbersMinChars: 5,
24042
+ glyphMargin: true,
24043
+ folding: true,
24044
+ // Monaco's default is 10; 12 more become the breathing strip.
24045
+ lineDecorationsWidth: 22,
24046
+ breathing: 12,
24047
+ numberInset: 0
24048
+ };
24049
+ var COMPACT_GUTTER = {
24050
+ lineNumbersMinChars: 2,
24051
+ glyphMargin: false,
24052
+ folding: false,
24053
+ // 4px number inset, 6px between the digits and the seam, 6px breathing.
24054
+ lineDecorationsWidth: 16,
24055
+ breathing: 6,
24056
+ numberInset: 4
24057
+ };
24058
+ function useCompactGutter(host) {
24059
+ const [compact, setCompact] = useState42(
24060
+ () => typeof window !== "undefined" && window.innerWidth < COMPACT_GUTTER_MAX_WIDTH
24061
+ );
24062
+ useLayoutEffect11(() => {
24063
+ if (!host) return;
24064
+ const apply = (width) => {
24065
+ if (width > 0) setCompact(width < COMPACT_GUTTER_MAX_WIDTH);
24066
+ };
24067
+ apply(host.getBoundingClientRect().width);
24068
+ if (typeof ResizeObserver === "undefined") return;
24069
+ const observer = new ResizeObserver((entries) => {
24070
+ const entry = entries[entries.length - 1];
24071
+ if (entry) apply(entry.contentRect.width);
24072
+ });
24073
+ observer.observe(host);
24074
+ return () => observer.disconnect();
24075
+ }, [host]);
24076
+ return compact;
24077
+ }
23631
24078
  function useDocumentMonacoLanguages(language, source) {
23632
24079
  const [requested, setRequested] = useState42(
23633
24080
  () => monacoLanguagesForDocument(language, source)
23634
24081
  );
23635
- useEffect34(() => {
24082
+ useEffect35(() => {
23636
24083
  const delay = normalizeMonacoLanguage(language) === "markdown" ? 250 : 0;
23637
24084
  const timer = window.setTimeout(() => {
23638
24085
  setRequested(monacoLanguagesForDocument(language, source));
@@ -23652,6 +24099,8 @@ function RawEditor({
23652
24099
  readOnly = false
23653
24100
  }) {
23654
24101
  const contextMenuOwnedByShell = useEditorContextMenuAvailable();
24102
+ const [editorHost, setEditorHost] = useState42(null);
24103
+ const { breathing, numberInset, ...gutterOptions } = useCompactGutter(editorHost) ? COMPACT_GUTTER : FULL_GUTTER;
23655
24104
  const {
23656
24105
  editorSource,
23657
24106
  setEditorSource,
@@ -23681,14 +24130,14 @@ function RawEditor({
23681
24130
  const keyDisposable = useRef38(null);
23682
24131
  const submitOnEnterRef = useRef38(submitOnEnter);
23683
24132
  const readOnlyRef = useRef38(readOnly);
23684
- useEffect34(() => {
24133
+ useEffect35(() => {
23685
24134
  submitOnEnterRef.current = submitOnEnter;
23686
24135
  }, [submitOnEnter]);
23687
- useEffect34(() => {
24136
+ useEffect35(() => {
23688
24137
  readOnlyRef.current = readOnly;
23689
24138
  }, [readOnly]);
23690
24139
  const mentionProviderRef = useRef38(mentionProvider);
23691
- useEffect34(() => {
24140
+ useEffect35(() => {
23692
24141
  mentionProviderRef.current = mentionProvider;
23693
24142
  }, [mentionProvider]);
23694
24143
  const docCustomTemplates = doc?.customTemplates;
@@ -23699,7 +24148,7 @@ function RawEditor({
23699
24148
  detail: "Block template"
23700
24149
  }));
23701
24150
  }
23702
- useEffect34(() => {
24151
+ useEffect35(() => {
23703
24152
  const builtIns = getAvailableTemplates().map((name) => ({
23704
24153
  name,
23705
24154
  detail: "Block template"
@@ -24088,7 +24537,7 @@ function RawEditor({
24088
24537
  },
24089
24538
  [setMonacoEditor, language]
24090
24539
  );
24091
- useEffect34(() => {
24540
+ useEffect35(() => {
24092
24541
  return () => {
24093
24542
  setMonacoEditor(null);
24094
24543
  completionDisposable.current?.dispose();
@@ -24118,7 +24567,7 @@ function RawEditor({
24118
24567
  },
24119
24568
  [setEditorSource]
24120
24569
  );
24121
- useEffect34(() => {
24570
+ useEffect35(() => {
24122
24571
  const editor = editorRef.current;
24123
24572
  if (editor) {
24124
24573
  const currentValue = editor.getValue();
@@ -24129,7 +24578,7 @@ function RawEditor({
24129
24578
  }
24130
24579
  }
24131
24580
  }, [editorSource]);
24132
- useEffect34(() => {
24581
+ useEffect35(() => {
24133
24582
  const editor = editorRef.current;
24134
24583
  if (!editor || !monacoNs) return;
24135
24584
  if (language !== "markdown") return;
@@ -24216,52 +24665,67 @@ function RawEditor({
24216
24665
  }
24217
24666
  );
24218
24667
  }
24219
- return /* @__PURE__ */ jsx54("div", { className, style: { width: "100%", height: "100%" }, "data-testid": "raw-editor", children: /* @__PURE__ */ jsx54(
24220
- Ft,
24668
+ return /* @__PURE__ */ jsx54(
24669
+ "div",
24221
24670
  {
24222
- defaultLanguage: language,
24223
- value: editorSource,
24224
- theme: effectiveTheme,
24225
- beforeMount: handleBeforeMount,
24226
- onMount: handleMount,
24227
- onChange: handleChange,
24228
- options: {
24229
- ariaLabel,
24230
- fontSize,
24231
- wordWrap,
24232
- minimap: { enabled: minimap },
24233
- lineNumbers: "on",
24234
- scrollBeyondLastLine: false,
24235
- automaticLayout: true,
24236
- tabSize: 2,
24237
- renderWhitespace: "selection",
24238
- bracketPairColorization: { enabled: true },
24239
- guides: { indentation: true },
24240
- padding: { top: 12, bottom: 12 },
24241
- // Markdown's tokenizer classifies most body text as "string"
24242
- // or "comment" context, which suppresses `quickSuggestions`
24243
- // by default. Enable in all three so our `{[icon]}` and
24244
- // `@mention` typeaheads keep firing as the user types past
24245
- // the trigger character.
24246
- quickSuggestions: { other: true, comments: true, strings: true },
24247
- suggestOnTriggerCharacters: true,
24248
- // Breathing room between the gutter and the first character.
24249
- // Done via Monaco's own option so cursor + hit-testing stay in
24250
- // sync — CSS-padding `.view-lines` shifts the text but not the
24251
- // cursors layer, which causes the cursor to drift left of the
24252
- // model column. Monaco's default is 10. We set 22 (12 extra),
24253
- // and CSS paints the rightmost 12px of the gutter as canvas
24254
- // color so the breathing room looks like it sits *inside* the
24255
- // canvas rather than widening the gutter.
24256
- lineDecorationsWidth: 22,
24257
- // EditorShell owns one consistent, host-extensible menu across
24258
- // Source, Write, and Preview. Proofing still intercepts squiggles.
24259
- contextmenu: !contextMenuOwnedByShell,
24260
- readOnly,
24261
- domReadOnly: readOnly
24262
- }
24671
+ ref: setEditorHost,
24672
+ className,
24673
+ style: {
24674
+ width: "100%",
24675
+ height: "100%",
24676
+ ["--squisq-raw-editor-breathing"]: `${breathing}px`,
24677
+ ["--squisq-raw-editor-number-inset"]: `${numberInset}px`
24678
+ },
24679
+ "data-testid": "raw-editor",
24680
+ children: /* @__PURE__ */ jsx54(
24681
+ Ft,
24682
+ {
24683
+ defaultLanguage: language,
24684
+ value: editorSource,
24685
+ theme: effectiveTheme,
24686
+ beforeMount: handleBeforeMount,
24687
+ onMount: handleMount,
24688
+ onChange: handleChange,
24689
+ options: {
24690
+ ariaLabel,
24691
+ fontSize,
24692
+ wordWrap,
24693
+ minimap: { enabled: minimap },
24694
+ lineNumbers: "on",
24695
+ scrollBeyondLastLine: false,
24696
+ automaticLayout: true,
24697
+ tabSize: 2,
24698
+ renderWhitespace: "selection",
24699
+ bracketPairColorization: { enabled: true },
24700
+ guides: { indentation: true },
24701
+ padding: { top: 12, bottom: 12 },
24702
+ // Markdown's tokenizer classifies most body text as "string"
24703
+ // or "comment" context, which suppresses `quickSuggestions`
24704
+ // by default. Enable in all three so our `{[icon]}` and
24705
+ // `@mention` typeaheads keep firing as the user types past
24706
+ // the trigger character.
24707
+ quickSuggestions: { other: true, comments: true, strings: true },
24708
+ suggestOnTriggerCharacters: true,
24709
+ // Line numbers, glyph margin, folding and the breathing room
24710
+ // between the gutter and the first character (see FULL_GUTTER).
24711
+ // The breathing room is done via Monaco's own
24712
+ // `lineDecorationsWidth` so cursor + hit-testing stay in sync —
24713
+ // CSS-padding `.view-lines` shifts the text but not the cursors
24714
+ // layer, which causes the cursor to drift left of the model
24715
+ // column. CSS then paints the rightmost `breathing` px of the
24716
+ // gutter as canvas color so the room looks like it sits *inside*
24717
+ // the canvas rather than widening the gutter.
24718
+ ...gutterOptions,
24719
+ // EditorShell owns one consistent, host-extensible menu across
24720
+ // Source, Write, and Preview. Proofing still intercepts squiggles.
24721
+ contextmenu: !contextMenuOwnedByShell,
24722
+ readOnly,
24723
+ domReadOnly: readOnly
24724
+ }
24725
+ }
24726
+ )
24263
24727
  }
24264
- ) });
24728
+ );
24265
24729
  }
24266
24730
 
24267
24731
  // src/headingBuild.ts
@@ -24627,10 +25091,31 @@ function DiagramCanvas({
24627
25091
  );
24628
25092
  const incomingNodesRef = useRef39(incomingNodes);
24629
25093
  incomingNodesRef.current = incomingNodes;
25094
+ const sceneRef = useRef39(scene);
25095
+ sceneRef.current = scene;
24630
25096
  const textConfig = useMemo27(
24631
25097
  () => ({
24632
25098
  channel: textChannel,
24633
25099
  resolveEditableId: (id) => nodeIdFromCardLayerId(id) ? id : null,
25100
+ // The card is what's edited; its label is what the text looks like.
25101
+ // A container's title sits at the top of the card, a node's label in
25102
+ // the middle.
25103
+ textStyle: (id) => {
25104
+ const { layers, nodes } = sceneRef.current;
25105
+ const label = layers.find((l2) => l2.id === labelLayerIdForCard(id));
25106
+ if (label?.type !== "text") return void 0;
25107
+ const { fontSize, fontFamily, fontWeight, lineHeight, textAlign } = label.content.style;
25108
+ const nodeId = nodeIdFromCardLayerId(id);
25109
+ const container = nodes.find((n) => n.id === nodeId)?.kind === "container";
25110
+ return {
25111
+ fontSize,
25112
+ fontFamily,
25113
+ fontWeight,
25114
+ lineHeight,
25115
+ textAlign,
25116
+ verticalAlign: container ? "top" : "middle"
25117
+ };
25118
+ },
24634
25119
  getHtml: (id) => {
24635
25120
  const nodeId = nodeIdFromCardLayerId(id);
24636
25121
  const node2 = incomingNodesRef.current.find((n) => n.id === nodeId);
@@ -24862,7 +25347,7 @@ function removeNodeOp(diagram, nodeId) {
24862
25347
  }
24863
25348
 
24864
25349
  // src/asciiDiagram/asciiDiagramData.ts
24865
- import { useEffect as useEffect36, useMemo as useMemo29, useState as useState45 } from "react";
25350
+ import { useEffect as useEffect37, useMemo as useMemo29, useState as useState45 } from "react";
24866
25351
  import {
24867
25352
  asciiCellToCanvas,
24868
25353
  asciiDiagramToTemplateData,
@@ -24883,7 +25368,7 @@ import {
24883
25368
  } from "@bendyline/squisq/doc";
24884
25369
 
24885
25370
  // src/asciiDiagram/AsciiDiagramWidget.tsx
24886
- import { useCallback as useCallback35, useEffect as useEffect35, useMemo as useMemo28, useRef as useRef40, useState as useState44 } from "react";
25371
+ import { useCallback as useCallback35, useEffect as useEffect36, useMemo as useMemo28, useRef as useRef40, useState as useState44 } from "react";
24887
25372
 
24888
25373
  // src/asciiDiagram/asciiDiagramCommands.ts
24889
25374
  import {
@@ -25155,7 +25640,7 @@ function AsciiDiagramWidget({
25155
25640
  return asciiDiagramToCanvas(offsetAsciiDiagram(view.diagram, offset));
25156
25641
  }, [view, canvasOffsetVersion]);
25157
25642
  const [rejectedAt, setRejectedAt] = useState44(null);
25158
- useEffect35(() => {
25643
+ useEffect36(() => {
25159
25644
  if (rejectedAt === null) return;
25160
25645
  const timer = setTimeout(() => setRejectedAt(null), REJECTED_NOTICE_MS);
25161
25646
  return () => clearTimeout(timer);
@@ -25590,7 +26075,7 @@ function asciiDiagramToCanvas(diagram) {
25590
26075
  }
25591
26076
  function useAsciiDiagramData(editor, blockId) {
25592
26077
  const [version, setVersion] = useState45(0);
25593
- useEffect36(() => {
26078
+ useEffect37(() => {
25594
26079
  const onUpdate = () => setVersion((v2) => v2 + 1);
25595
26080
  editor.on("transaction", onUpdate);
25596
26081
  return () => {
@@ -27436,7 +27921,7 @@ function mermaidErrorMessage(error) {
27436
27921
  }
27437
27922
 
27438
27923
  // src/mermaid/mermaidSurfaceTheme.ts
27439
- import { useEffect as useEffect37, useMemo as useMemo30, useState as useState46 } from "react";
27924
+ import { useEffect as useEffect38, useMemo as useMemo30, useState as useState46 } from "react";
27440
27925
  import { relativeLuminance } from "@bendyline/squisq/schemas";
27441
27926
  var EDITOR_SURFACE = {
27442
27927
  light: {
@@ -27473,7 +27958,7 @@ function resolveMermaidSurfaceTheme(host, theme) {
27473
27958
  }
27474
27959
  function useMermaidSurfaceTheme(host, theme) {
27475
27960
  const [surface, setSurface] = useState46(() => readHostSurface(host));
27476
- useEffect37(() => {
27961
+ useEffect38(() => {
27477
27962
  const sync = () => setSurface((current) => {
27478
27963
  const next = readHostSurface(host);
27479
27964
  return current.scheme === next.scheme && current.inheritsThemeColors === next.inheritsThemeColors ? current : next;
@@ -27499,7 +27984,7 @@ function useMermaidSurfaceTheme(host, theme) {
27499
27984
  }
27500
27985
 
27501
27986
  // src/mermaid/MermaidDiagramCanvas.tsx
27502
- import { useCallback as useCallback36, useEffect as useEffect38, useId as useId12, useLayoutEffect as useLayoutEffect10, useRef as useRef41, useState as useState47 } from "react";
27987
+ import { useCallback as useCallback36, useEffect as useEffect39, useId as useId12, useLayoutEffect as useLayoutEffect12, useRef as useRef41, useState as useState47 } from "react";
27503
27988
  import { jsx as jsx57, jsxs as jsxs43 } from "react/jsx-runtime";
27504
27989
  var renderSequence = 0;
27505
27990
  function excludesCanvasPan(target, viewport) {
@@ -27576,7 +28061,7 @@ function MermaidDiagramCanvas({
27576
28061
  const [panning, setPanning] = useState47(false);
27577
28062
  const [selectionAnchor, setSelectionAnchor] = useState47(null);
27578
28063
  const [renameValue, setRenameValue] = useState47("");
27579
- useEffect38(() => {
28064
+ useEffect39(() => {
27580
28065
  if (!panning) return;
27581
28066
  const finish = () => {
27582
28067
  panDragRef.current = null;
@@ -27604,7 +28089,7 @@ function MermaidDiagramCanvas({
27604
28089
  window.removeEventListener("blur", finish);
27605
28090
  };
27606
28091
  }, [panning]);
27607
- useEffect38(() => {
28092
+ useEffect39(() => {
27608
28093
  let current = true;
27609
28094
  const id = `squisq-mermaid-svg-${++renderSequence}`;
27610
28095
  setRendering(true);
@@ -27629,7 +28114,7 @@ function MermaidDiagramCanvas({
27629
28114
  current = false;
27630
28115
  };
27631
28116
  }, [source, onModelChange, theme]);
27632
- useLayoutEffect10(() => {
28117
+ useLayoutEffect12(() => {
27633
28118
  const scroll = scrollRef.current;
27634
28119
  if (!scroll) return;
27635
28120
  const measure = () => {
@@ -27650,7 +28135,7 @@ function MermaidDiagramCanvas({
27650
28135
  observer.observe(scroll);
27651
28136
  return () => observer.disconnect();
27652
28137
  }, []);
27653
- useLayoutEffect10(() => {
28138
+ useLayoutEffect12(() => {
27654
28139
  setContentSize(readSvgViewBox(svgRootRef.current));
27655
28140
  }, [svg]);
27656
28141
  const fitZoom = contentSize && canvasSize ? calculateFitScale(contentSize, canvasSize) : 1;
@@ -27659,7 +28144,7 @@ function MermaidDiagramCanvas({
27659
28144
  setZoom(fitZoom);
27660
28145
  setPan({ x: 0, y: 0 });
27661
28146
  }, [fitZoom]);
27662
- useLayoutEffect10(() => {
28147
+ useLayoutEffect12(() => {
27663
28148
  if (viewMode !== "fit") return;
27664
28149
  setZoom(fitZoom);
27665
28150
  setPan({ x: 0, y: 0 });
@@ -27726,7 +28211,7 @@ function MermaidDiagramCanvas({
27726
28211
  selectedNodeId,
27727
28212
  selectedTextId
27728
28213
  ]);
27729
- useLayoutEffect10(() => {
28214
+ useLayoutEffect12(() => {
27730
28215
  const node2 = renamingNodeId ? model2?.nodes.find((candidate) => candidate.id === renamingNodeId) : null;
27731
28216
  const edge2 = renamingEdgeId ? model2?.edges.find((candidate) => candidate.id === renamingEdgeId) : null;
27732
28217
  const text = renamingTextId ? model2 && mermaidEditableTexts(model2).find((candidate) => candidate.id === renamingTextId) : null;
@@ -27739,7 +28224,7 @@ function MermaidDiagramCanvas({
27739
28224
  });
27740
28225
  return () => window.cancelAnimationFrame(frame);
27741
28226
  }, [model2, renamingEdgeId, renamingNodeId, renamingTextId]);
27742
- useLayoutEffect10(() => {
28227
+ useLayoutEffect12(() => {
27743
28228
  const root = svgRootRef.current;
27744
28229
  if (!root || !model2) {
27745
28230
  setSelectionAnchor(null);
@@ -27875,7 +28360,7 @@ function MermaidDiagramCanvas({
27875
28360
  updateSelectionAnchor,
27876
28361
  zoom
27877
28362
  ]);
27878
- useEffect38(() => {
28363
+ useEffect39(() => {
27879
28364
  const scroll = scrollRef.current;
27880
28365
  if (!scroll) return;
27881
28366
  const update = () => updateSelectionAnchor();
@@ -28280,7 +28765,7 @@ function CanvasAction({
28280
28765
  }
28281
28766
 
28282
28767
  // src/mermaid/MermaidShapePalette.tsx
28283
- import { useEffect as useEffect39, useLayoutEffect as useLayoutEffect11, useMemo as useMemo31, useRef as useRef42, useState as useState48 } from "react";
28768
+ import { useEffect as useEffect40, useLayoutEffect as useLayoutEffect13, useMemo as useMemo31, useRef as useRef42, useState as useState48 } from "react";
28284
28769
  import { jsx as jsx58, jsxs as jsxs44 } from "react/jsx-runtime";
28285
28770
  var CATEGORIES = ["Basic", "Process", "Data", "Documents", "Symbols"];
28286
28771
  var PALETTE_WIDTH = 360;
@@ -28291,7 +28776,7 @@ function MermaidShapePalette({ selected, onPick, onClose }) {
28291
28776
  const [query, setQuery] = useState48("");
28292
28777
  const [position, setPosition] = useState48(null);
28293
28778
  const ref = useRef42(null);
28294
- useEffect39(() => {
28779
+ useEffect40(() => {
28295
28780
  const onPointerDown = (event) => {
28296
28781
  const target = event.target;
28297
28782
  if (target?.closest(".squisq-scene-block-toolbar")) return;
@@ -28316,7 +28801,7 @@ function MermaidShapePalette({ selected, onPick, onClose }) {
28316
28801
  )
28317
28802
  })).filter((section) => section.shapes.length > 0);
28318
28803
  }, [query]);
28319
- useLayoutEffect11(() => {
28804
+ useLayoutEffect13(() => {
28320
28805
  const palette = ref.current;
28321
28806
  const anchor = palette?.parentElement;
28322
28807
  if (!palette || !anchor) return;
@@ -28462,7 +28947,7 @@ function MermaidShapeThumb({ shape }) {
28462
28947
  }
28463
28948
 
28464
28949
  // src/mermaid/mermaidData.ts
28465
- import { useEffect as useEffect41, useMemo as useMemo32, useState as useState50 } from "react";
28950
+ import { useEffect as useEffect42, useMemo as useMemo32, useState as useState50 } from "react";
28466
28951
 
28467
28952
  // src/mermaid/MermaidDiagramExtension.ts
28468
28953
  import { Extension as Extension7 } from "@tiptap/core";
@@ -28473,8 +28958,8 @@ import { createElement as createElement10 } from "react";
28473
28958
  // src/mermaid/MermaidDiagramWidget.tsx
28474
28959
  import {
28475
28960
  useCallback as useCallback37,
28476
- useEffect as useEffect40,
28477
- useLayoutEffect as useLayoutEffect12,
28961
+ useEffect as useEffect41,
28962
+ useLayoutEffect as useLayoutEffect14,
28478
28963
  useRef as useRef43,
28479
28964
  useState as useState49,
28480
28965
  useSyncExternalStore as useSyncExternalStore2
@@ -29113,7 +29598,7 @@ function MermaidDiagramWidget({
29113
29598
  },
29114
29599
  [effectiveHeight]
29115
29600
  );
29116
- useEffect40(() => {
29601
+ useEffect41(() => {
29117
29602
  if (!editNotice) return;
29118
29603
  const timeout = window.setTimeout(() => setEditNotice(""), 7e3);
29119
29604
  return () => window.clearTimeout(timeout);
@@ -29350,7 +29835,7 @@ function MermaidDiagramWidget({
29350
29835
  ] });
29351
29836
  }
29352
29837
  function useDismissibleMermaidPopover(ref, onClose) {
29353
- useEffect40(() => {
29838
+ useEffect41(() => {
29354
29839
  const onPointerDown = (event) => {
29355
29840
  const target = event.target;
29356
29841
  if (target?.closest(".squisq-scene-block-toolbar")) return;
@@ -29369,7 +29854,7 @@ function useDismissibleMermaidPopover(ref, onClose) {
29369
29854
  }
29370
29855
  function useClampedMermaidPopoverPosition(ref, preferredWidth, preferredMaxHeight) {
29371
29856
  const [position, setPosition] = useState49(null);
29372
- useLayoutEffect12(() => {
29857
+ useLayoutEffect14(() => {
29373
29858
  const picker = ref.current;
29374
29859
  const anchor = picker?.parentElement;
29375
29860
  if (!picker || !anchor) return;
@@ -29730,7 +30215,7 @@ var MermaidDiagramExtension = Extension7.create({
29730
30215
  // src/mermaid/mermaidData.ts
29731
30216
  function useMermaidDiagramData(editor, blockId) {
29732
30217
  const [version, setVersion] = useState50(0);
29733
- useEffect41(() => {
30218
+ useEffect42(() => {
29734
30219
  const onTransaction = () => setVersion((value) => value + 1);
29735
30220
  editor.on("transaction", onTransaction);
29736
30221
  return () => {
@@ -29755,7 +30240,7 @@ function describeSvgIslandRemovals(diagnostics) {
29755
30240
  }
29756
30241
 
29757
30242
  // src/svgIsland/SvgIslandWidget.tsx
29758
- import { useEffect as useEffect43, useMemo as useMemo33, useState as useState52 } from "react";
30243
+ import { useEffect as useEffect44, useMemo as useMemo33, useState as useState52 } from "react";
29759
30244
  import { parseSvgIsland } from "@bendyline/squisq/markdown";
29760
30245
  import { SvgIslandImage } from "@bendyline/squisq-react";
29761
30246
 
@@ -29811,7 +30296,7 @@ function FenceMonacoEditor({
29811
30296
  }
29812
30297
 
29813
30298
  // src/codeSnippet/fenceEditorHooks.ts
29814
- import { useEffect as useEffect42, useState as useState51 } from "react";
30299
+ import { useEffect as useEffect43, useState as useState51 } from "react";
29815
30300
  function schemeFromHost(host) {
29816
30301
  return host?.closest("[data-theme]")?.dataset.theme === "dark" ? "dark" : "light";
29817
30302
  }
@@ -29819,7 +30304,7 @@ function useHostColorScheme(host) {
29819
30304
  const [scheme, setScheme] = useState51(
29820
30305
  () => host != null && !host.isConnected ? null : schemeFromHost(host)
29821
30306
  );
29822
- useEffect42(() => {
30307
+ useEffect43(() => {
29823
30308
  if (host != null && !host.isConnected) {
29824
30309
  setScheme(null);
29825
30310
  const raf = requestAnimationFrame(() => {
@@ -29838,7 +30323,7 @@ function useHostColorScheme(host) {
29838
30323
  }
29839
30324
  function useEditorEditable(editor) {
29840
30325
  const [editable, setEditable] = useState51(editor.isEditable);
29841
- useEffect42(() => {
30326
+ useEffect43(() => {
29842
30327
  const sync = () => setEditable(editor.isEditable);
29843
30328
  editor.on("update", sync);
29844
30329
  editor.on("transaction", sync);
@@ -30001,7 +30486,7 @@ import { jsx as jsx61, jsxs as jsxs46 } from "react/jsx-runtime";
30001
30486
  var nextModelInstanceId = 0;
30002
30487
  function useSvgIslandView(editor, blockId) {
30003
30488
  const [version, setVersion] = useState52(0);
30004
- useEffect43(() => {
30489
+ useEffect44(() => {
30005
30490
  const onTransaction = () => setVersion((value) => value + 1);
30006
30491
  editor.on("transaction", onTransaction);
30007
30492
  return () => {
@@ -30103,13 +30588,13 @@ import { Decoration as Decoration8, DecorationSet as DecorationSet8 } from "@tip
30103
30588
  import { createElement as createElement12 } from "react";
30104
30589
 
30105
30590
  // src/codeSnippet/CodeSnippetWidget.tsx
30106
- import { useCallback as useCallback38, useEffect as useEffect45, useRef as useRef44, useState as useState54 } from "react";
30591
+ import { useCallback as useCallback38, useEffect as useEffect46, useRef as useRef44, useState as useState54 } from "react";
30107
30592
 
30108
30593
  // src/codeSnippet/codeSnippetData.ts
30109
- import { useEffect as useEffect44, useMemo as useMemo34, useState as useState53 } from "react";
30594
+ import { useEffect as useEffect45, useMemo as useMemo34, useState as useState53 } from "react";
30110
30595
  function useCodeSnippetData(editor, blockId) {
30111
30596
  const [version, setVersion] = useState53(0);
30112
- useEffect44(() => {
30597
+ useEffect45(() => {
30113
30598
  const onTransaction = () => setVersion((value) => value + 1);
30114
30599
  editor.on("transaction", onTransaction);
30115
30600
  return () => {
@@ -30176,7 +30661,7 @@ function CodeSnippetWidget({
30176
30661
  sourceRef.current = data?.source ?? "";
30177
30662
  const languageRef = useRef44("");
30178
30663
  languageRef.current = data?.fenceLanguage ?? "";
30179
- useEffect45(
30664
+ useEffect46(
30180
30665
  () => () => {
30181
30666
  if (copyResetTimer.current !== void 0) clearTimeout(copyResetTimer.current);
30182
30667
  },
@@ -30421,7 +30906,7 @@ import { Decoration as Decoration9, DecorationSet as DecorationSet9 } from "@tip
30421
30906
  import { createElement as createElement13 } from "react";
30422
30907
 
30423
30908
  // src/fenceWidgets/HostFenceWidget.tsx
30424
- import { Component as Component2, useEffect as useEffect46, useMemo as useMemo35, useState as useState55 } from "react";
30909
+ import { Component as Component2, useEffect as useEffect47, useMemo as useMemo35, useState as useState55 } from "react";
30425
30910
  import { jsx as jsx63 } from "react/jsx-runtime";
30426
30911
  var HostFenceErrorBoundary = class extends Component2 {
30427
30912
  constructor() {
@@ -30437,7 +30922,7 @@ var HostFenceErrorBoundary = class extends Component2 {
30437
30922
  };
30438
30923
  function HostFenceWidget({ editor, blockId, getRenderers, getTheme }) {
30439
30924
  const [version, setVersion] = useState55(0);
30440
- useEffect46(() => {
30925
+ useEffect47(() => {
30441
30926
  const onUpdate = () => setVersion((v2) => v2 + 1);
30442
30927
  editor.on("transaction", onUpdate);
30443
30928
  return () => {
@@ -30943,7 +31428,7 @@ import { createElement as createElement14 } from "react";
30943
31428
  import { isDataFilePath } from "@bendyline/squisq/doc";
30944
31429
 
30945
31430
  // src/dataCard/DataCardWidget.tsx
30946
- import { useCallback as useCallback39, useEffect as useEffect47, useMemo as useMemo36, useRef as useRef45, useState as useState56 } from "react";
31431
+ import { useCallback as useCallback39, useEffect as useEffect48, useMemo as useMemo36, useRef as useRef45, useState as useState56 } from "react";
30947
31432
  import { EMPTY_TABLE_VIEW_STATE } from "@bendyline/squisq/table";
30948
31433
 
30949
31434
  // src/dataCard/gridSave.ts
@@ -31347,7 +31832,7 @@ var XLSX_SESSION_LOCKED_REASON = "Date cells and shared-formula masters stay loc
31347
31832
  var pendingFormulas = /* @__PURE__ */ new WeakMap();
31348
31833
  function useCardLink(editor, blockId) {
31349
31834
  const [version, setVersion] = useState56(0);
31350
- useEffect47(() => {
31835
+ useEffect48(() => {
31351
31836
  const bump = () => setVersion((v2) => v2 + 1);
31352
31837
  editor.on("transaction", bump);
31353
31838
  return () => {
@@ -31367,7 +31852,7 @@ function useCardLink(editor, blockId) {
31367
31852
  }
31368
31853
  function useMediaRevision(getMediaRevision) {
31369
31854
  const [revision, setRevision] = useState56(() => getMediaRevision());
31370
- useEffect47(() => {
31855
+ useEffect48(() => {
31371
31856
  const timer = window.setInterval(() => {
31372
31857
  const next = getMediaRevision();
31373
31858
  setRevision((prev) => prev === next ? prev : next);
@@ -31381,6 +31866,13 @@ function formatBytes2(size) {
31381
31866
  if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
31382
31867
  return `${(size / (1024 * 1024)).toFixed(1)} MB`;
31383
31868
  }
31869
+ function decodeHrefForDisplay(href) {
31870
+ try {
31871
+ return decodeURIComponent(href);
31872
+ } catch {
31873
+ return href;
31874
+ }
31875
+ }
31384
31876
  function DataCardWidget({
31385
31877
  editor,
31386
31878
  blockId,
@@ -31411,7 +31903,7 @@ function DataCardWidget({
31411
31903
  regionParams.anchor,
31412
31904
  regionParams.headerRow
31413
31905
  ]);
31414
- useEffect47(() => {
31906
+ useEffect48(() => {
31415
31907
  if (!href || cardPos === null) return;
31416
31908
  const provider = activeProvider;
31417
31909
  if (!provider) {
@@ -31573,7 +32065,7 @@ function DataCardWidget({
31573
32065
  cancelled = true;
31574
32066
  };
31575
32067
  }, [href, revision, activeProvider, activeContainer, regionIdentity, editor]);
31576
- useEffect47(
32068
+ useEffect48(
31577
32069
  () => () => {
31578
32070
  gridRef.current?.formulaSession?.dispose();
31579
32071
  gridRef.current?.provider.dispose();
@@ -31648,7 +32140,7 @@ function DataCardWidget({
31648
32140
  }
31649
32141
  }, [href, onMediaSaved]);
31650
32142
  if (!link || !href) return null;
31651
- const fileName = href.split("/").pop() ?? href;
32143
+ const fileName = decodeHrefForDisplay(href.split("/").pop() ?? href);
31652
32144
  const size = state.kind === "meta" || state.kind === "grid" ? state.size : null;
31653
32145
  const grid = state.kind === "grid" ? state.grid : null;
31654
32146
  const xlsxMeta = grid?.ingested.xlsx;
@@ -31662,7 +32154,7 @@ function DataCardWidget({
31662
32154
  children: [
31663
32155
  /* @__PURE__ */ jsxs48("div", { className: "squisq-data-card-header", children: [
31664
32156
  /* @__PURE__ */ jsx64("span", { className: "squisq-data-card-icon", "aria-hidden": "true", children: "\u{1F4CA}" }),
31665
- /* @__PURE__ */ jsx64("span", { className: "squisq-data-card-name", title: href, children: fileName }),
32157
+ /* @__PURE__ */ jsx64("span", { className: "squisq-data-card-name", title: decodeHrefForDisplay(href), children: fileName }),
31666
32158
  /* @__PURE__ */ jsx64("span", { className: "squisq-data-card-meta", children: [
31667
32159
  state.kind === "missing" ? "not found in this document\u2019s files" : null,
31668
32160
  state.kind === "loading" ? "loading\u2026" : null,
@@ -32335,7 +32827,7 @@ function pasteVideoEmbedCode(view, text) {
32335
32827
  }
32336
32828
 
32337
32829
  // src/treeview/treeViewData.ts
32338
- import { useEffect as useEffect48, useMemo as useMemo37, useState as useState58 } from "react";
32830
+ import { useEffect as useEffect49, useMemo as useMemo37, useState as useState58 } from "react";
32339
32831
 
32340
32832
  // src/treeview/TreeViewExtension.ts
32341
32833
  import { Extension as Extension15 } from "@tiptap/core";
@@ -33044,7 +33536,7 @@ var TreeViewExtension = Extension15.create({
33044
33536
  // src/treeview/treeViewData.ts
33045
33537
  function useTreeViewData(editor, blockId) {
33046
33538
  const [version, setVersion] = useState58(0);
33047
- useEffect48(() => {
33539
+ useEffect49(() => {
33048
33540
  const onUpdate = () => setVersion((v2) => v2 + 1);
33049
33541
  editor.on("transaction", onUpdate);
33050
33542
  return () => {
@@ -33070,7 +33562,7 @@ function shouldPasteAsTreeFence(text) {
33070
33562
  }
33071
33563
 
33072
33564
  // src/timeline/timelineData.ts
33073
- import { useEffect as useEffect50, useMemo as useMemo39, useRef as useRef48, useState as useState60 } from "react";
33565
+ import { useEffect as useEffect51, useMemo as useMemo39, useRef as useRef48, useState as useState60 } from "react";
33074
33566
 
33075
33567
  // src/timeline/TimelineViewExtension.ts
33076
33568
  import { Extension as Extension16 } from "@tiptap/core";
@@ -33084,7 +33576,7 @@ import {
33084
33576
  } from "@bendyline/squisq/doc";
33085
33577
 
33086
33578
  // src/timeline/TimelineEditorWidget.tsx
33087
- import { useCallback as useCallback41, useEffect as useEffect49, useMemo as useMemo38, useRef as useRef47, useState as useState59 } from "react";
33579
+ import { useCallback as useCallback41, useEffect as useEffect50, useMemo as useMemo38, useRef as useRef47, useState as useState59 } from "react";
33088
33580
  import {
33089
33581
  asciiTimelineToTemplateData
33090
33582
  } from "@bendyline/squisq/doc";
@@ -33635,7 +34127,7 @@ function TimelineEditorWidget({ editor, blockId }) {
33635
34127
  }
33636
34128
  return null;
33637
34129
  }, [selectedEventId, view]);
33638
- useEffect49(() => {
34130
+ useEffect50(() => {
33639
34131
  if (!view) return;
33640
34132
  const ids = new Set(
33641
34133
  view.timeline.tracks.flatMap((track) => track.events.map((event) => event.id))
@@ -33643,13 +34135,13 @@ function TimelineEditorWidget({ editor, blockId }) {
33643
34135
  if (selectedEventId && ids.has(selectedEventId)) return;
33644
34136
  setSelectedEventId(view.timeline.tracks[0]?.events[0]?.id ?? null);
33645
34137
  }, [selectedEventId, view]);
33646
- useEffect49(() => {
34138
+ useEffect50(() => {
33647
34139
  if (editable) return;
33648
34140
  setAddingTrackId(null);
33649
34141
  setEditingTrackId(null);
33650
34142
  setHover(null);
33651
34143
  }, [editable]);
33652
- useEffect49(() => {
34144
+ useEffect50(() => {
33653
34145
  if (!editingTrackId || view?.timeline.tracks.some((track) => track.id === editingTrackId)) {
33654
34146
  return;
33655
34147
  }
@@ -34056,7 +34548,7 @@ function InlineTrackLabel({
34056
34548
  onFinish
34057
34549
  }) {
34058
34550
  const [draft, setDraft] = useState59(label);
34059
- useEffect49(() => {
34551
+ useEffect50(() => {
34060
34552
  if (editing) setDraft(label);
34061
34553
  }, [editing, label]);
34062
34554
  const commitLabel = () => {
@@ -34115,8 +34607,8 @@ function EventInspector({
34115
34607
  const { event } = selected;
34116
34608
  const [label, setLabel] = useState59(event.label);
34117
34609
  const [description, setDescription] = useState59(event.description ?? "");
34118
- useEffect49(() => setLabel(event.label), [event.label]);
34119
- useEffect49(() => setDescription(event.description ?? ""), [event.description]);
34610
+ useEffect50(() => setLabel(event.label), [event.label]);
34611
+ useEffect50(() => setDescription(event.description ?? ""), [event.description]);
34120
34612
  const update = (patch) => dispatch(patch);
34121
34613
  const commitLabel = () => {
34122
34614
  const next = label.trim();
@@ -34426,7 +34918,7 @@ var TimelineViewExtension = Extension16.create({
34426
34918
  function useTimelineData(editor, blockId) {
34427
34919
  const [version, setVersion] = useState60(0);
34428
34920
  const dataCache = useRef48(null);
34429
- useEffect50(() => {
34921
+ useEffect51(() => {
34430
34922
  const onEditorChange = () => setVersion((value) => value + 1);
34431
34923
  editor.on("transaction", onEditorChange);
34432
34924
  editor.on("update", onEditorChange);
@@ -34461,7 +34953,7 @@ function shouldPasteAsTimelineFence(text) {
34461
34953
  }
34462
34954
 
34463
34955
  // src/BlockPropertiesPopover.tsx
34464
- import { useEffect as useEffect51, useId as useId13, useRef as useRef49, useState as useState61 } from "react";
34956
+ import { useEffect as useEffect52, useId as useId13, useRef as useRef49, useState as useState61 } from "react";
34465
34957
  import { createPortal as createPortal14 } from "react-dom";
34466
34958
  import { Fragment as Fragment22, jsx as jsx67, jsxs as jsxs51 } from "react/jsx-runtime";
34467
34959
  var TRANSITION_FLYOUT = ".squisq-transition-flyout";
@@ -34480,10 +34972,10 @@ function BlockPropertiesPopover({
34480
34972
  const [inner, setInner] = useState61(blockAttrs);
34481
34973
  const [templateInner, setTemplateInner] = useState61(templateParams);
34482
34974
  const [style, setStyle] = useState61(() => computeStyle3(anchorRect));
34483
- useEffect51(() => {
34975
+ useEffect52(() => {
34484
34976
  requestAnimationFrame(() => setStyle(computeStyle3(anchorRect, panelRef.current)));
34485
34977
  }, [anchorRect]);
34486
- useEffect51(() => {
34978
+ useEffect52(() => {
34487
34979
  const onKey = (e2) => {
34488
34980
  if (e2.key === "Escape") onClose();
34489
34981
  };
@@ -34734,7 +35226,7 @@ function persistFromWrite(bodyMd, state) {
34734
35226
  }
34735
35227
 
34736
35228
  // src/WysiwygEditor.tsx
34737
- import { useCallback as useCallback43, useEffect as useEffect56, useMemo as useMemo42, useRef as useRef52, useState as useState67 } from "react";
35229
+ import { useCallback as useCallback43, useEffect as useEffect57, useMemo as useMemo42, useRef as useRef52, useState as useState67 } from "react";
34738
35230
  import { useEditor as useEditor2, EditorContent as EditorContent2 } from "@tiptap/react";
34739
35231
  import { Selection as Selection2 } from "@tiptap/pm/state";
34740
35232
  import { Extension as Extension18 } from "@tiptap/core";
@@ -34779,7 +35271,7 @@ import Link2 from "@tiptap/extension-link";
34779
35271
  // src/WikiLink.tsx
34780
35272
  import { Node as Node2, InputRule } from "@tiptap/core";
34781
35273
  import { NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react";
34782
- import { useEffect as useEffect52, useState as useState62 } from "react";
35274
+ import { useEffect as useEffect53, useState as useState62 } from "react";
34783
35275
  import { wikiLinkPath as wikiLinkPath3, parseWikiLink as parseWikiLink3 } from "@bendyline/squisq/markdown";
34784
35276
 
34785
35277
  // src/wikiSuggestionPlugin.ts
@@ -34952,7 +35444,7 @@ function WikiLinkView({ node: node2, editor }) {
34952
35444
  const [exists, setExists] = useState62(null);
34953
35445
  const [error, setError] = useState62("");
34954
35446
  const [busy, setBusy] = useState62(false);
34955
- useEffect52(() => {
35447
+ useEffect53(() => {
34956
35448
  let cancelled = false;
34957
35449
  setExists(null);
34958
35450
  setError("");
@@ -35147,7 +35639,7 @@ var InlineIcon = Node3.create({
35147
35639
  });
35148
35640
 
35149
35641
  // src/ImageNodeView.tsx
35150
- import { useEffect as useEffect53, useRef as useRef50, useState as useState63 } from "react";
35642
+ import { useEffect as useEffect54, useRef as useRef50, useState as useState63 } from "react";
35151
35643
  import { NodeViewWrapper as NodeViewWrapper2, ReactNodeViewRenderer as ReactNodeViewRenderer2 } from "@tiptap/react";
35152
35644
  import Image from "@tiptap/extension-image";
35153
35645
  import { mergeAttributes as mergeAttributes3 } from "@tiptap/core";
@@ -35181,7 +35673,7 @@ function ImageComponent({ node: node2, selected, editor, updateAttributes }) {
35181
35673
  const sanitizedSvg = useSanitizedSvgImage(loadedSrc, isSvg);
35182
35674
  const resolvedSrc = sanitizedSvg ?? "";
35183
35675
  const resolutionFailed = needsResolution && currentResolution?.url === null || sanitizedSvg === null;
35184
- useEffect53(() => {
35676
+ useEffect54(() => {
35185
35677
  if (isUploading || !mediaProvider || !resolveAs) return;
35186
35678
  setImageLoad(null);
35187
35679
  let cancelled = false;
@@ -35520,15 +36012,15 @@ function MediaEditButton({ src, kind, fx, className }) {
35520
36012
 
35521
36013
  // src/tiptap/TiptapVideo.tsx
35522
36014
  import { NodeViewWrapper as NodeViewWrapper3, ReactNodeViewRenderer as ReactNodeViewRenderer3 } from "@tiptap/react";
35523
- import { useEffect as useEffect55, useState as useState65 } from "react";
36015
+ import { useEffect as useEffect56, useState as useState65 } from "react";
35524
36016
 
35525
36017
  // src/tiptap/useResolvedMediaSrc.ts
35526
- import { useEffect as useEffect54, useState as useState64 } from "react";
36018
+ import { useEffect as useEffect55, useState as useState64 } from "react";
35527
36019
  function useResolvedMediaSrc(src) {
35528
36020
  const { mediaProvider, mediaRevision } = useEditorContext();
35529
36021
  const isRelative = !!src && !src.startsWith("blob:") && !src.startsWith("http:") && !src.startsWith("https:") && !src.startsWith("data:") && !src.startsWith("/");
35530
36022
  const [resolved, setResolved] = useState64(isRelative && mediaProvider ? "" : src);
35531
- useEffect54(() => {
36023
+ useEffect55(() => {
35532
36024
  if (!mediaProvider || !isRelative) {
35533
36025
  setResolved(src);
35534
36026
  return;
@@ -35587,7 +36079,7 @@ function VideoMedia({
35587
36079
  const resolvedSrc = useResolvedMediaSrc(src ?? "");
35588
36080
  const [audioOnly, setAudioOnly] = useState65(false);
35589
36081
  const resolvedPoster = useResolvedMediaSrc(poster ?? "");
35590
- useEffect55(() => {
36082
+ useEffect56(() => {
35591
36083
  setAudioOnly(false);
35592
36084
  }, [resolvedSrc]);
35593
36085
  const handleLoadedMetadata = (event) => {
@@ -36918,7 +37410,7 @@ function WysiwygEditor({
36918
37410
  mermaidThemeStoreRef.current = createMermaidThemeStore(activeTheme ?? DEFAULT_THEME4);
36919
37411
  }
36920
37412
  const mermaidThemeStore = mermaidThemeStoreRef.current;
36921
- useEffect56(() => {
37413
+ useEffect57(() => {
36922
37414
  mermaidThemeStore.setTheme(activeTheme ?? DEFAULT_THEME4);
36923
37415
  }, [activeTheme, mermaidThemeStore]);
36924
37416
  const { docTemplates, onDocTemplatesChange } = useDocCustomTemplates();
@@ -36938,19 +37430,19 @@ function WysiwygEditor({
36938
37430
  [docTemplates, onDocTemplatesChange]
36939
37431
  );
36940
37432
  const mentionProviderRef = useRef52(mentionProvider);
36941
- useEffect56(() => {
37433
+ useEffect57(() => {
36942
37434
  mentionProviderRef.current = mentionProvider;
36943
37435
  }, [mentionProvider]);
36944
37436
  const fenceRenderersRef = useRef52(fenceRenderers);
36945
- useEffect56(() => {
37437
+ useEffect57(() => {
36946
37438
  fenceRenderersRef.current = fenceRenderers;
36947
37439
  }, [fenceRenderers]);
36948
37440
  const onCopyCodeRef = useRef52(onCopyCode);
36949
- useEffect56(() => {
37441
+ useEffect57(() => {
36950
37442
  onCopyCodeRef.current = onCopyCode;
36951
37443
  }, [onCopyCode]);
36952
37444
  const activeThemeRef = useRef52(activeTheme);
36953
- useEffect56(() => {
37445
+ useEffect57(() => {
36954
37446
  activeThemeRef.current = activeTheme;
36955
37447
  }, [activeTheme]);
36956
37448
  const resolvedPlaceholder = useMemo42(() => placeholder ?? pickEmptyPrompt(), [placeholder]);
@@ -36962,30 +37454,30 @@ function WysiwygEditor({
36962
37454
  const pendingLocalSourcesRef = useRef52([]);
36963
37455
  const mediaProviderRef = useRef52(mediaProvider);
36964
37456
  const mediaRevisionRef = useRef52(mediaRevision);
36965
- useEffect56(() => {
37457
+ useEffect57(() => {
36966
37458
  mediaRevisionRef.current = mediaRevision;
36967
37459
  }, [mediaRevision]);
36968
37460
  const onOpenFilesPanelRef = useRef52(onOpenFilesPanel);
36969
- useEffect56(() => {
37461
+ useEffect57(() => {
36970
37462
  onOpenFilesPanelRef.current = onOpenFilesPanel;
36971
37463
  }, [onOpenFilesPanel]);
36972
37464
  const calcEngineFactoryRef = useRef52(calcEngineFactory);
36973
- useEffect56(() => {
37465
+ useEffect57(() => {
36974
37466
  calcEngineFactoryRef.current = calcEngineFactory;
36975
37467
  }, [calcEngineFactory]);
36976
37468
  const workspaceContainerRef = useRef52(workspaceContainer);
36977
- useEffect56(() => {
37469
+ useEffect57(() => {
36978
37470
  workspaceContainerRef.current = workspaceContainer;
36979
37471
  }, [workspaceContainer]);
36980
37472
  const bumpMediaRevisionRef = useRef52(bumpMediaRevision);
36981
- useEffect56(() => {
37473
+ useEffect57(() => {
36982
37474
  bumpMediaRevisionRef.current = bumpMediaRevision;
36983
37475
  }, [bumpMediaRevision]);
36984
- useEffect56(() => {
37476
+ useEffect57(() => {
36985
37477
  mediaProviderRef.current = mediaProvider;
36986
37478
  }, [mediaProvider]);
36987
37479
  const [accessoryPaths, setAccessoryPaths] = useState67(() => /* @__PURE__ */ new Set());
36988
- useEffect56(() => {
37480
+ useEffect57(() => {
36989
37481
  if (!mediaProvider) {
36990
37482
  setAccessoryPaths(/* @__PURE__ */ new Set());
36991
37483
  return;
@@ -37025,7 +37517,7 @@ function WysiwygEditor({
37025
37517
  initialContentRef.current = markdownToTiptap(initialDisplayBodyRef.current);
37026
37518
  }
37027
37519
  const submitOnEnterRef = useRef52(submitOnEnter);
37028
- useEffect56(() => {
37520
+ useEffect57(() => {
37029
37521
  submitOnEnterRef.current = submitOnEnter;
37030
37522
  }, [submitOnEnter]);
37031
37523
  const editor = useEditor2({
@@ -37260,7 +37752,7 @@ function WysiwygEditor({
37260
37752
  }
37261
37753
  }
37262
37754
  });
37263
- useEffect56(() => {
37755
+ useEffect57(() => {
37264
37756
  let cancelled = false;
37265
37757
  queueMicrotask(() => {
37266
37758
  if (!cancelled && editor && !editor.isDestroyed)
@@ -37270,13 +37762,13 @@ function WysiwygEditor({
37270
37762
  cancelled = true;
37271
37763
  };
37272
37764
  }, [editor, wikiLinks]);
37273
- useEffect56(() => {
37765
+ useEffect57(() => {
37274
37766
  if (editor) {
37275
37767
  setTiptapEditor(editor);
37276
37768
  }
37277
37769
  return () => setTiptapEditor(null);
37278
37770
  }, [editor, setTiptapEditor]);
37279
- useEffect56(() => {
37771
+ useEffect57(() => {
37280
37772
  if (!editor) return;
37281
37773
  const syncPlaceholders = () => {
37282
37774
  syncAccessoryFileLinkPlaceholders(editor.view.dom, accessoryPaths);
@@ -37287,7 +37779,7 @@ function WysiwygEditor({
37287
37779
  editor.off("update", syncPlaceholders);
37288
37780
  };
37289
37781
  }, [accessoryPaths, editor]);
37290
- useEffect56(() => {
37782
+ useEffect57(() => {
37291
37783
  if (editor) editor.setEditable(!readOnly);
37292
37784
  }, [editor, readOnly]);
37293
37785
  const containerRef = useRef52(null);
@@ -37299,7 +37791,7 @@ function WysiwygEditor({
37299
37791
  if (editor && !editor.isDestroyed) editor.commands.focus();
37300
37792
  });
37301
37793
  }, [editor]);
37302
- useEffect56(() => {
37794
+ useEffect57(() => {
37303
37795
  if (!editor) return;
37304
37796
  const root = containerRef.current;
37305
37797
  if (!root) return;
@@ -37355,7 +37847,7 @@ function WysiwygEditor({
37355
37847
  root.addEventListener("mousedown", onClick);
37356
37848
  return () => root.removeEventListener("mousedown", onClick);
37357
37849
  }, [editor]);
37358
- useEffect56(() => {
37850
+ useEffect57(() => {
37359
37851
  if (!editor) return;
37360
37852
  const previousLayout = sourceLayoutRef.current;
37361
37853
  const layoutChanged = previousLayout !== layoutMode;
@@ -37466,7 +37958,7 @@ function WysiwygEditor({
37466
37958
  }
37467
37959
  return out;
37468
37960
  }, [activeTheme, themeInheritance]);
37469
- useEffect56(() => {
37961
+ useEffect57(() => {
37470
37962
  if (!editor || editor.isDestroyed) return;
37471
37963
  editor.view.dispatch(editor.state.tr.setMeta(DOCUMENT_TRAY_KEY, layoutMode === "document"));
37472
37964
  }, [editor, layoutMode]);
@@ -37700,19 +38192,19 @@ function insertDroppedMedia(view, event, point, mediaProvider, onMediaUploaded)
37700
38192
  }
37701
38193
 
37702
38194
  // src/InlinePreviewGutter.tsx
37703
- import { useEffect as useEffect59, useLayoutEffect as useLayoutEffect13, useMemo as useMemo45, useRef as useRef53, useState as useState70 } from "react";
38195
+ import { useEffect as useEffect60, useLayoutEffect as useLayoutEffect15, useMemo as useMemo45, useRef as useRef53, useState as useState70 } from "react";
37704
38196
  import { VIEWPORT_PRESETS as VIEWPORT_PRESETS5 } from "@bendyline/squisq/schemas";
37705
38197
  import { BlockRenderer as BlockRenderer3, MediaContext as MediaContext4 } from "@bendyline/squisq-react";
37706
38198
 
37707
38199
  // src/useHeadingLayout.ts
37708
- import { useCallback as useCallback44, useEffect as useEffect57, useMemo as useMemo43, useState as useState68 } from "react";
38200
+ import { useCallback as useCallback44, useEffect as useEffect58, useMemo as useMemo43, useState as useState68 } from "react";
37709
38201
  import { flattenBlocks as flattenBlocks3, hasTemplate } from "@bendyline/squisq/doc";
37710
38202
  function useHeadingLayout(refInsideWrapper) {
37711
38203
  const { doc, activeView, monacoEditor, tiptapEditor } = useEditorContext();
37712
38204
  const flatBlocks = useMemo43(() => doc ? flattenBlocks3(doc.blocks) : [], [doc]);
37713
38205
  const [entries, setEntries] = useState68([]);
37714
38206
  const [pageEdges, setPageEdges] = useState68(null);
37715
- useEffect57(() => {
38207
+ useEffect58(() => {
37716
38208
  if (activeView !== "wysiwyg") return;
37717
38209
  const node2 = refInsideWrapper.current;
37718
38210
  if (!node2) return;
@@ -37780,7 +38272,7 @@ function useHeadingLayout(refInsideWrapper) {
37780
38272
  window.removeEventListener("resize", recompute);
37781
38273
  };
37782
38274
  }, [activeView, flatBlocks, refInsideWrapper]);
37783
- useEffect57(() => {
38275
+ useEffect58(() => {
37784
38276
  if (activeView !== "raw") return;
37785
38277
  if (!monacoEditor) return;
37786
38278
  const node2 = refInsideWrapper.current;
@@ -37841,7 +38333,7 @@ function useHeadingLayout(refInsideWrapper) {
37841
38333
  window.removeEventListener("resize", recompute);
37842
38334
  };
37843
38335
  }, [activeView, monacoEditor, flatBlocks, refInsideWrapper]);
37844
- useEffect57(() => {
38336
+ useEffect58(() => {
37845
38337
  setEntries([]);
37846
38338
  setPageEdges(null);
37847
38339
  }, [activeView]);
@@ -37925,7 +38417,7 @@ import { flattenRenderableBlocks, DEFAULT_THEME as DEFAULT_THEME5 } from "@bendy
37925
38417
  import { extractPlainText as extractPlainText3 } from "@bendyline/squisq/markdown";
37926
38418
 
37927
38419
  // src/usePreviewProjection.ts
37928
- import { useEffect as useEffect58, useState as useState69 } from "react";
38420
+ import { useEffect as useEffect59, useState as useState69 } from "react";
37929
38421
  import {
37930
38422
  resolveAudioMapping,
37931
38423
  resolveDataReferences
@@ -37972,7 +38464,7 @@ function preserveEquivalentAudio(previous, next) {
37972
38464
  }
37973
38465
  function usePreviewProjection(doc, transformStyle, workspaceContainer, documentTitle2, mediaRevision) {
37974
38466
  const [projection, setProjection] = useState69(null);
37975
- useEffect58(() => {
38467
+ useEffect59(() => {
37976
38468
  if (!doc || !doc.blocks.length) {
37977
38469
  setProjection(null);
37978
38470
  return;
@@ -38071,7 +38563,7 @@ function InlinePreviewGutter({
38071
38563
  }, [headingEntries]);
38072
38564
  const [positions, setPositions] = useState70(/* @__PURE__ */ new Map());
38073
38565
  const itemIds = items.map((item) => item.id).join("\0");
38074
- useEffect59(() => {
38566
+ useEffect60(() => {
38075
38567
  const gutter = gutterRef.current;
38076
38568
  if (!gutter || typeof IntersectionObserver === "undefined") return;
38077
38569
  let active = true;
@@ -38110,7 +38602,7 @@ function InlinePreviewGutter({
38110
38602
  observer.disconnect();
38111
38603
  };
38112
38604
  }, [itemIds, positions]);
38113
- useLayoutEffect13(() => {
38605
+ useLayoutEffect15(() => {
38114
38606
  if (items.length === 0) {
38115
38607
  setPositions((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
38116
38608
  return;
@@ -38309,7 +38801,7 @@ function InlinePreviewGutter({
38309
38801
  import {
38310
38802
  memo,
38311
38803
  useCallback as useCallback45,
38312
- useEffect as useEffect60,
38804
+ useEffect as useEffect61,
38313
38805
  useMemo as useMemo46,
38314
38806
  useRef as useRef54,
38315
38807
  useState as useState71
@@ -38782,10 +39274,10 @@ function useActiveOutlineBlockId() {
38782
39274
  flatBlocksRef.current = flatBlocks;
38783
39275
  const headingLocations = flatBlocks.map((block) => `${block.id}:${block.sourceHeading?.position?.start.line ?? ""}`).join("\0");
38784
39276
  const [activeId, setActiveId] = useState71(null);
38785
- useEffect60(() => {
39277
+ useEffect61(() => {
38786
39278
  setActiveId(null);
38787
39279
  }, [activeView]);
38788
- useEffect60(() => {
39280
+ useEffect61(() => {
38789
39281
  if (activeView !== "wysiwyg" || !tiptapEditor) return;
38790
39282
  const update = () => {
38791
39283
  const { from } = tiptapEditor.state.selection;
@@ -38807,7 +39299,7 @@ function useActiveOutlineBlockId() {
38807
39299
  tiptapEditor.off("update", update);
38808
39300
  };
38809
39301
  }, [activeView, tiptapEditor, headingLocations]);
38810
- useEffect60(() => {
39302
+ useEffect61(() => {
38811
39303
  if (activeView !== "raw" || !monacoEditor) return;
38812
39304
  const update = () => {
38813
39305
  const line = monacoEditor.getPosition()?.lineNumber;
@@ -38894,7 +39386,7 @@ function bumpHeadingLevelInSource(source, line, delta) {
38894
39386
  }
38895
39387
 
38896
39388
  // src/codeContext/CodeContextZones.tsx
38897
- import { useCallback as useCallback47, useEffect as useEffect62, useMemo as useMemo48, useRef as useRef56, useState as useState72 } from "react";
39389
+ import { useCallback as useCallback47, useEffect as useEffect63, useMemo as useMemo48, useRef as useRef56, useState as useState72 } from "react";
38898
39390
  import { createPortal as createPortal15 } from "react-dom";
38899
39391
 
38900
39392
  // src/codeContext/diffContextSections.ts
@@ -39018,7 +39510,7 @@ function setOrdinal(zone, ordinal) {
39018
39510
  // src/codeContext/CodeContextSectionView.tsx
39019
39511
  import { parseMarkdown as parseMarkdown8 } from "@bendyline/squisq/markdown";
39020
39512
  import { MarkdownRenderer } from "@bendyline/squisq-react";
39021
- import { useCallback as useCallback46, useEffect as useEffect61, useMemo as useMemo47, useRef as useRef55 } from "react";
39513
+ import { useCallback as useCallback46, useEffect as useEffect62, useMemo as useMemo47, useRef as useRef55 } from "react";
39022
39514
  import { jsx as jsx77, jsxs as jsxs61 } from "react/jsx-runtime";
39023
39515
  function CodeContextSectionView({
39024
39516
  section,
@@ -39038,7 +39530,7 @@ function CodeContextSectionView({
39038
39530
  () => expanded && section.markdown ? parseMarkdown8(section.markdown).children : null,
39039
39531
  [expanded, section.markdown]
39040
39532
  );
39041
- useEffect61(() => {
39533
+ useEffect62(() => {
39042
39534
  const el = rootRef.current;
39043
39535
  if (!el || typeof ResizeObserver === "undefined") return;
39044
39536
  const report = () => {
@@ -39114,7 +39606,7 @@ function CodeContextZones({ options }) {
39114
39606
  const [, setZonesVersion] = useState72(0);
39115
39607
  const [expandedById, setExpandedById] = useState72({});
39116
39608
  const seenIds = useRef56(/* @__PURE__ */ new Set());
39117
- useEffect62(() => {
39609
+ useEffect63(() => {
39118
39610
  if (!monacoEditor) return;
39119
39611
  const mgr = new CodeContextZoneManager(monacoEditor);
39120
39612
  const off = mgr.onDidChangeZones(() => setZonesVersion((v2) => v2 + 1));
@@ -39137,7 +39629,7 @@ function CodeContextZones({ options }) {
39137
39629
  });
39138
39630
  return out;
39139
39631
  }, [fileTop, sections]);
39140
- useEffect62(() => {
39632
+ useEffect63(() => {
39141
39633
  if (!manager) return;
39142
39634
  manager.sync(resolved.map((r) => r.spec));
39143
39635
  setExpandedById((prev) => {
@@ -39603,7 +40095,7 @@ function placeClipInBlock(source, fromLine, targetHeadingLine, spec, startAt) {
39603
40095
  }
39604
40096
 
39605
40097
  // src/TimelineTrack.tsx
39606
- import { useCallback as useCallback49, useEffect as useEffect65, useMemo as useMemo49, useRef as useRef59, useState as useState75 } from "react";
40098
+ import { useCallback as useCallback49, useEffect as useEffect66, useMemo as useMemo49, useRef as useRef59, useState as useState75 } from "react";
39607
40099
  import {
39608
40100
  resolveMediaSchedule as resolveMediaSchedule2,
39609
40101
  getDocPlaybackDuration,
@@ -39640,7 +40132,7 @@ var BlockThumbnail = memo2(function BlockThumbnail2({
39640
40132
  });
39641
40133
 
39642
40134
  // src/useTimelineClock.ts
39643
- import { useCallback as useCallback48, useEffect as useEffect63, useRef as useRef57, useState as useState73 } from "react";
40135
+ import { useCallback as useCallback48, useEffect as useEffect64, useRef as useRef57, useState as useState73 } from "react";
39644
40136
  function advanceTime(prev, dt, total) {
39645
40137
  if (total <= 0) return 0;
39646
40138
  return Math.min(total, Math.max(0, prev + dt));
@@ -39669,10 +40161,10 @@ function useTimelineClock(total) {
39669
40161
  const mediaHostRef = useRef57(null);
39670
40162
  currentTimeRef.current = currentTime;
39671
40163
  isPlayingRef.current = isPlaying;
39672
- useEffect63(() => {
40164
+ useEffect64(() => {
39673
40165
  setCurrentTime((t) => Math.min(t, Math.max(0, total)));
39674
40166
  }, [total]);
39675
- useEffect63(() => {
40167
+ useEffect64(() => {
39676
40168
  if (!isPlaying) return;
39677
40169
  lastRef.current = performance.now();
39678
40170
  const tick = (now) => {
@@ -39770,7 +40262,7 @@ function timelineBars(clips) {
39770
40262
  }
39771
40263
 
39772
40264
  // src/TimelineItemMenu.tsx
39773
- import { useEffect as useEffect64, useLayoutEffect as useLayoutEffect14, useRef as useRef58, useState as useState74 } from "react";
40265
+ import { useEffect as useEffect65, useLayoutEffect as useLayoutEffect16, useRef as useRef58, useState as useState74 } from "react";
39774
40266
  import { createPortal as createPortal16 } from "react-dom";
39775
40267
  import { Fragment as Fragment28, jsx as jsx81, jsxs as jsxs63 } from "react/jsx-runtime";
39776
40268
  function TimelineItemMenu({
@@ -39788,7 +40280,7 @@ function TimelineItemMenu({
39788
40280
  }) {
39789
40281
  const panelRef = useRef58(null);
39790
40282
  const [style, setStyle] = useState74(() => menuStyle(anchor));
39791
- useLayoutEffect14(() => {
40283
+ useLayoutEffect16(() => {
39792
40284
  const panel = panelRef.current;
39793
40285
  if (!panel) return;
39794
40286
  const updatePosition = () => setStyle(menuStyle(anchor, panel));
@@ -39801,7 +40293,7 @@ function TimelineItemMenu({
39801
40293
  resizeObserver?.disconnect();
39802
40294
  };
39803
40295
  }, [anchor]);
39804
- useEffect64(() => {
40296
+ useEffect65(() => {
39805
40297
  const onKeyDown = (event) => {
39806
40298
  if (event.key !== "Escape") return;
39807
40299
  event.preventDefault();
@@ -40289,7 +40781,7 @@ function TimelineTrack({
40289
40781
  play();
40290
40782
  }, [play]);
40291
40783
  const [scrubbing, setScrubbing] = useState75(false);
40292
- useEffect65(() => {
40784
+ useEffect66(() => {
40293
40785
  if (!scrubbing) return;
40294
40786
  const onMove = (e2) => {
40295
40787
  const scroll = scrollRef.current;
@@ -40327,7 +40819,7 @@ function TimelineTrack({
40327
40819
  [blocks]
40328
40820
  );
40329
40821
  const followedBlockRef = useRef59(null);
40330
- useEffect65(() => {
40822
+ useEffect66(() => {
40331
40823
  if (!isPlaying) {
40332
40824
  followedBlockRef.current = null;
40333
40825
  return;
@@ -40338,7 +40830,7 @@ function TimelineTrack({
40338
40830
  const line = headingLine(block);
40339
40831
  if (line != null) goToBlockByLine(line);
40340
40832
  }, [isPlaying, currentTime, blockAtTime, goToBlockByLine]);
40341
- useEffect65(() => {
40833
+ useEffect66(() => {
40342
40834
  if (!isPlaying) return;
40343
40835
  const scroll = scrollRef.current;
40344
40836
  if (!scroll) return;
@@ -40387,7 +40879,7 @@ function TimelineTrack({
40387
40879
  const zoomIn = useCallback49(() => setPxPerSecond((s) => Math.min(ZOOM_MAX, s * ZOOM_FACTOR)), []);
40388
40880
  const zoomOut = useCallback49(() => setPxPerSecond((s) => Math.max(ZOOM_MIN, s / ZOOM_FACTOR)), []);
40389
40881
  const [viewportWidth, setViewportWidth] = useState75(0);
40390
- useEffect65(() => {
40882
+ useEffect66(() => {
40391
40883
  const el = scrollRef.current;
40392
40884
  if (!el || typeof ResizeObserver === "undefined") return;
40393
40885
  const update = () => setViewportWidth(el.clientWidth);
@@ -40401,7 +40893,7 @@ function TimelineTrack({
40401
40893
  const dragRef = useRef59(null);
40402
40894
  dragRef.current = drag;
40403
40895
  const isDragging = drag != null && !drag.committed;
40404
- useEffect65(() => {
40896
+ useEffect66(() => {
40405
40897
  if (!isDragging) return;
40406
40898
  const onMove = (e2) => {
40407
40899
  const d = dragRef.current;
@@ -40432,7 +40924,7 @@ function TimelineTrack({
40432
40924
  window.removeEventListener("pointerup", onUp);
40433
40925
  };
40434
40926
  }, [isDragging]);
40435
- useEffect65(() => {
40927
+ useEffect66(() => {
40436
40928
  if (dragRef.current?.committed) setDrag(null);
40437
40929
  }, [doc]);
40438
40930
  const beginDrag = useCallback49(
@@ -41294,7 +41786,7 @@ function TimelineToolbar({
41294
41786
  }
41295
41787
 
41296
41788
  // src/PlainHtmlPreview.tsx
41297
- import { useCallback as useCallback50, useEffect as useEffect66, useMemo as useMemo52, useRef as useRef60, useState as useState76 } from "react";
41789
+ import { useCallback as useCallback50, useEffect as useEffect67, useMemo as useMemo52, useRef as useRef60, useState as useState76 } from "react";
41298
41790
  import {
41299
41791
  isSvgImageSource as isSvgImageSource2,
41300
41792
  parseMarkdown as parseMarkdown9,
@@ -41358,12 +41850,29 @@ function loadPreparation() {
41358
41850
  import("@bendyline/squisq-formats/html-icons"),
41359
41851
  import("@bendyline/squisq-formats/diagrams"),
41360
41852
  import("@bendyline/squisq-react/diagram-pictures")
41361
- ]).then(([prep, icons, diagrams, pictures]) => ({
41362
- needs: prep.needsPlainHtmlPreparation,
41363
- prepare: prep.prepareMarkdownDocForPlainHtml,
41364
- renderDiagram: diagrams.cachingDiagramRasterizer(pictures.createDiagramPictureRenderer()),
41365
- outlineIcons: icons.plainHtmlIcons
41366
- })));
41853
+ ]).then(([prep, icons, diagrams, pictures]) => {
41854
+ const unthemed = diagrams.cachingDiagramRasterizer(pictures.createDiagramPictureRenderer());
41855
+ const themed = /* @__PURE__ */ new Map();
41856
+ return {
41857
+ needs: prep.needsPlainHtmlPreparation,
41858
+ prepare: prep.prepareMarkdownDocForPlainHtml,
41859
+ diagramRenderer: (theme) => {
41860
+ if (!theme) return unthemed;
41861
+ const key = JSON.stringify(theme);
41862
+ let renderer = themed.get(key);
41863
+ if (!renderer) {
41864
+ renderer = diagrams.cachingDiagramRasterizer(
41865
+ pictures.createDiagramPictureRenderer({ theme, background: theme.colors.background })
41866
+ );
41867
+ themed.set(key, renderer);
41868
+ const oldest = themed.size > 8 ? themed.keys().next().value : void 0;
41869
+ if (oldest !== void 0) themed.delete(oldest);
41870
+ }
41871
+ return renderer;
41872
+ },
41873
+ outlineIcons: icons.plainHtmlIcons
41874
+ };
41875
+ }));
41367
41876
  return preparationPromise;
41368
41877
  }
41369
41878
  var PREPARE_DELAY_MS = 300;
@@ -41404,7 +41913,7 @@ function PlainHtmlPreview({
41404
41913
  const parsedDoc = useMemo52(() => parseMarkdown9(markdown), [markdown]);
41405
41914
  const [wikiProjection, setWikiProjection] = useState76(null);
41406
41915
  const mdDoc = wikiProjection?.source === parsedDoc && wikiProjection.support === wikiLinks ? wikiProjection.doc : parsedDoc;
41407
- useEffect66(() => {
41916
+ useEffect67(() => {
41408
41917
  if (!wikiLinks) return;
41409
41918
  let cancelled = false;
41410
41919
  void resolveWikiLinks(
@@ -41423,7 +41932,7 @@ function PlainHtmlPreview({
41423
41932
  };
41424
41933
  }, [parsedDoc, wikiLinks]);
41425
41934
  const [resolvedImages, setResolvedImages] = useState76(null);
41426
- useEffect66(() => {
41935
+ useEffect67(() => {
41427
41936
  if (!mediaProvider) {
41428
41937
  setResolvedImages(null);
41429
41938
  return;
@@ -41460,7 +41969,7 @@ function PlainHtmlPreview({
41460
41969
  }, [resolvedImages, images]);
41461
41970
  const iconsCss = useMemo52(() => collectInlineFontAwesomeCss(), []);
41462
41971
  const [preparation, setPreparation] = useState76(null);
41463
- useEffect66(() => {
41972
+ useEffect67(() => {
41464
41973
  let cancelled = false;
41465
41974
  loadPreparation().then(
41466
41975
  (loaded) => {
@@ -41477,22 +41986,23 @@ function PlainHtmlPreview({
41477
41986
  () => container ? dataTableCacheFor(container, mediaRevision ?? 0) : void 0,
41478
41987
  [container, mediaRevision]
41479
41988
  );
41989
+ const renderDiagram = useMemo52(() => preparation?.diagramRenderer(theme), [preparation, theme]);
41480
41990
  const needsPreparation = useMemo52(
41481
- () => preparation?.needs(mdDoc, {
41482
- renderDiagram: preparation.renderDiagram,
41991
+ () => !!preparation && !!renderDiagram && preparation.needs(mdDoc, {
41992
+ renderDiagram,
41483
41993
  outlineIcons: preparation.outlineIcons,
41484
41994
  container
41485
- }) ?? false,
41486
- [preparation, mdDoc, container]
41995
+ }),
41996
+ [preparation, renderDiagram, mdDoc, container]
41487
41997
  );
41488
41998
  const [prepared, setPrepared] = useState76(null);
41489
41999
  const preparedOnceRef = useRef60(false);
41490
- useEffect66(() => {
41491
- if (!preparation || !needsPreparation) return;
42000
+ useEffect67(() => {
42001
+ if (!preparation || !renderDiagram || !needsPreparation) return;
41492
42002
  let cancelled = false;
41493
42003
  const prepare = () => {
41494
42004
  preparation.prepare(mdDoc, {
41495
- renderDiagram: preparation.renderDiagram,
42005
+ renderDiagram,
41496
42006
  outlineIcons: preparation.outlineIcons,
41497
42007
  container,
41498
42008
  dataTableCache
@@ -41512,10 +42022,10 @@ function PlainHtmlPreview({
41512
42022
  cancelled = true;
41513
42023
  clearTimeout(timer);
41514
42024
  };
41515
- }, [preparation, needsPreparation, mdDoc, container, dataTableCache]);
42025
+ }, [preparation, renderDiagram, needsPreparation, mdDoc, container, dataTableCache]);
41516
42026
  const view = needsPreparation ? prepared : null;
41517
42027
  const [renderFn, setRenderFn] = useState76(() => cachedRender);
41518
- useEffect66(() => {
42028
+ useEffect67(() => {
41519
42029
  if (renderFn) return;
41520
42030
  let cancelled = false;
41521
42031
  loadRenderFn().then((fn) => {
@@ -41556,7 +42066,7 @@ function PlainHtmlPreview({
41556
42066
  frameDocument.addEventListener("click", handleClick, true);
41557
42067
  removeFrameLinkHandlerRef.current = () => frameDocument.removeEventListener("click", handleClick, true);
41558
42068
  }, [onLinkClick]);
41559
- useEffect66(() => {
42069
+ useEffect67(() => {
41560
42070
  installFrameLinkHandler();
41561
42071
  return () => removeFrameLinkHandlerRef.current();
41562
42072
  }, [html, installFrameLinkHandler]);
@@ -41652,11 +42162,11 @@ function PlainHtmlPreview({
41652
42162
  style2.remove();
41653
42163
  };
41654
42164
  }, [onCopyCode, showCodeCopyButton]);
41655
- useEffect66(() => {
42165
+ useEffect67(() => {
41656
42166
  installFrameCodeCopyControls();
41657
42167
  return () => removeFrameCodeCopyControlsRef.current();
41658
42168
  }, [html, installFrameCodeCopyControls]);
41659
- useEffect66(() => {
42169
+ useEffect67(() => {
41660
42170
  if (!globalKeyboardShortcuts) return;
41661
42171
  const handleKeyDown = (event) => {
41662
42172
  if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.key !== "ArrowDown" && event.key !== "ArrowUp") {
@@ -41741,7 +42251,7 @@ function collectImageRefs(doc) {
41741
42251
  }
41742
42252
 
41743
42253
  // src/PreviewPanel.tsx
41744
- import { useState as useState80, useEffect as useEffect71, useMemo as useMemo57, useCallback as useCallback54, useRef as useRef64 } from "react";
42254
+ import { useState as useState80, useEffect as useEffect72, useMemo as useMemo57, useCallback as useCallback54, useRef as useRef64 } from "react";
41745
42255
  import { createPortal as createPortal18 } from "react-dom";
41746
42256
  import {
41747
42257
  DashboardView,
@@ -41886,9 +42396,9 @@ import {
41886
42396
  createContext as createContext7,
41887
42397
  useCallback as useCallback51,
41888
42398
  useContext as useContext7,
41889
- useEffect as useEffect67,
42399
+ useEffect as useEffect68,
41890
42400
  useId as useId15,
41891
- useLayoutEffect as useLayoutEffect15,
42401
+ useLayoutEffect as useLayoutEffect17,
41892
42402
  useMemo as useMemo53,
41893
42403
  useRef as useRef61,
41894
42404
  useState as useState77
@@ -42153,12 +42663,12 @@ function PresentationModeProvider({
42153
42663
  },
42154
42664
  [availableTargets, selectedTarget, stop]
42155
42665
  );
42156
- useEffect67(() => {
42666
+ useEffect68(() => {
42157
42667
  if (availableTargets.includes(selectedTarget)) return;
42158
42668
  setSelectedTarget("control");
42159
42669
  if (activeTargetRef.current !== null) void stop();
42160
42670
  }, [availableTargets, selectedTarget, stop]);
42161
- useEffect67(() => {
42671
+ useEffect68(() => {
42162
42672
  const root = rootRef.current;
42163
42673
  const ownerDocument = root?.ownerDocument;
42164
42674
  if (!root || !ownerDocument) return;
@@ -42170,7 +42680,7 @@ function PresentationModeProvider({
42170
42680
  ownerDocument.addEventListener("fullscreenchange", handleFullscreenChange);
42171
42681
  return () => ownerDocument.removeEventListener("fullscreenchange", handleFullscreenChange);
42172
42682
  }, [rootRef]);
42173
- useEffect67(() => {
42683
+ useEffect68(() => {
42174
42684
  const root = rootRef.current;
42175
42685
  if (!root) return;
42176
42686
  if (activeTarget) root.dataset.presentationMode = activeTarget;
@@ -42179,10 +42689,10 @@ function PresentationModeProvider({
42179
42689
  delete root.dataset.presentationMode;
42180
42690
  };
42181
42691
  }, [activeTarget, rootRef]);
42182
- useEffect67(() => {
42692
+ useEffect68(() => {
42183
42693
  if (activeView !== "preview" && activeTargetRef.current !== null) void stop();
42184
42694
  }, [activeView, stop]);
42185
- useEffect67(() => {
42695
+ useEffect68(() => {
42186
42696
  const previous = previousActiveTargetRef.current;
42187
42697
  previousActiveTargetRef.current = activeTarget;
42188
42698
  if (previous === null || activeTarget !== null) return;
@@ -42190,7 +42700,7 @@ function PresentationModeProvider({
42190
42700
  returnFocusRef.current = null;
42191
42701
  if (target?.isConnected) target.focus();
42192
42702
  }, [activeTarget]);
42193
- useEffect67(() => {
42703
+ useEffect68(() => {
42194
42704
  if (activeTarget !== "control") return;
42195
42705
  const ownerDocument = rootRef.current?.ownerDocument;
42196
42706
  if (!ownerDocument) return;
@@ -42202,12 +42712,12 @@ function PresentationModeProvider({
42202
42712
  ownerDocument.addEventListener("keydown", handleKeyDown);
42203
42713
  return () => ownerDocument.removeEventListener("keydown", handleKeyDown);
42204
42714
  }, [activeTarget, rootRef, stop]);
42205
- useEffect67(() => {
42715
+ useEffect68(() => {
42206
42716
  if (!error) return;
42207
42717
  const timer = window.setTimeout(() => setError(null), 6e3);
42208
42718
  return () => window.clearTimeout(timer);
42209
42719
  }, [error]);
42210
- useLayoutEffect15(() => {
42720
+ useLayoutEffect17(() => {
42211
42721
  if (activeTarget !== "fullscreen") {
42212
42722
  setFullscreenExitStyle(null);
42213
42723
  return;
@@ -42237,7 +42747,7 @@ function PresentationModeProvider({
42237
42747
  window.removeEventListener("resize", updatePosition);
42238
42748
  };
42239
42749
  }, [activeTarget, rootRef]);
42240
- useEffect67(
42750
+ useEffect68(
42241
42751
  () => () => {
42242
42752
  popupCleanupRef.current?.();
42243
42753
  const popup = popupRef.current;
@@ -42366,7 +42876,7 @@ function PresentationModeControl() {
42366
42876
  updatePosition();
42367
42877
  setOpen(true);
42368
42878
  }, [updatePosition]);
42369
- useEffect67(() => {
42879
+ useEffect68(() => {
42370
42880
  if (!open) return;
42371
42881
  const handlePointerDown = (event) => {
42372
42882
  const target = event.target;
@@ -42389,7 +42899,7 @@ function PresentationModeControl() {
42389
42899
  window.removeEventListener("scroll", updatePosition, true);
42390
42900
  };
42391
42901
  }, [closeMenu, open, updatePosition]);
42392
- useLayoutEffect15(() => {
42902
+ useLayoutEffect17(() => {
42393
42903
  if (!open || !anchor) return;
42394
42904
  const checked = menuRef.current?.querySelector('[aria-checked="true"]');
42395
42905
  const first = menuRef.current?.querySelector('[role="menuitemradio"]');
@@ -42511,7 +43021,7 @@ import {
42511
43021
  createContext as createContext8,
42512
43022
  useCallback as useCallback52,
42513
43023
  useContext as useContext8,
42514
- useEffect as useEffect68,
43024
+ useEffect as useEffect69,
42515
43025
  useMemo as useMemo54,
42516
43026
  useRef as useRef62,
42517
43027
  useState as useState78
@@ -42581,7 +43091,7 @@ function PrintModeProvider({ rootRef, children }) {
42581
43091
  markPrinting();
42582
43092
  ownerWindow.print();
42583
43093
  }, [markPrinting, rootRef]);
42584
- useEffect68(() => {
43094
+ useEffect69(() => {
42585
43095
  const root = rootRef.current;
42586
43096
  if (!root) return;
42587
43097
  if (active) root.dataset.printPreview = "true";
@@ -42590,10 +43100,10 @@ function PrintModeProvider({ rootRef, children }) {
42590
43100
  delete root.dataset.printPreview;
42591
43101
  };
42592
43102
  }, [active, rootRef]);
42593
- useEffect68(() => {
43103
+ useEffect69(() => {
42594
43104
  if (activeView !== "preview" && active) close();
42595
43105
  }, [active, activeView, close]);
42596
- useEffect68(() => {
43106
+ useEffect69(() => {
42597
43107
  if (!active) return;
42598
43108
  const ownerWindow = rootRef.current?.ownerDocument.defaultView;
42599
43109
  const ownerDocument = rootRef.current?.ownerDocument;
@@ -42684,7 +43194,7 @@ function PrintPreviewToolbar() {
42684
43194
  }
42685
43195
 
42686
43196
  // src/print/PrintPreview.tsx
42687
- import { useCallback as useCallback53, useEffect as useEffect69, useMemo as useMemo55, useRef as useRef63 } from "react";
43197
+ import { useCallback as useCallback53, useEffect as useEffect70, useMemo as useMemo55, useRef as useRef63 } from "react";
42688
43198
  import {
42689
43199
  BlockRenderer as BlockRenderer5,
42690
43200
  FlashcardFaceView,
@@ -42741,7 +43251,7 @@ function PrintPreview({
42741
43251
  [contentDoc]
42742
43252
  );
42743
43253
  const isTextDocument = displayMode === "page" || displayMode === "narrate";
42744
- useEffect69(() => {
43254
+ useEffect70(() => {
42745
43255
  if (!isTextDocument) return registerPrintHandler(null);
42746
43256
  return registerPrintHandler(() => {
42747
43257
  const frameWindow = documentFrameRef.current?.contentWindow;
@@ -42880,7 +43390,7 @@ function PrintPreview({
42880
43390
  import {
42881
43391
  createContext as createContext9,
42882
43392
  useContext as useContext9,
42883
- useEffect as useEffect70,
43393
+ useEffect as useEffect71,
42884
43394
  useMemo as useMemo56,
42885
43395
  useState as useState79
42886
43396
  } from "react";
@@ -42987,7 +43497,7 @@ function SlideshowDataGridRenderer({
42987
43497
  const activeSourceKey = sourceKey(source);
42988
43498
  const [remote, setRemote] = useState79({ key: "", status: "idle" });
42989
43499
  const [gridModule, setGridModule] = useState79(null);
42990
- useEffect70(() => {
43500
+ useEffect71(() => {
42991
43501
  let active = true;
42992
43502
  void loadGridModule().then((module) => {
42993
43503
  if (active) setGridModule(module);
@@ -42996,7 +43506,7 @@ function SlideshowDataGridRenderer({
42996
43506
  active = false;
42997
43507
  };
42998
43508
  }, []);
42999
- useEffect70(() => {
43509
+ useEffect71(() => {
43000
43510
  if (!container || !source || !gridModule) {
43001
43511
  setRemote({ key: activeSourceKey, status: "idle" });
43002
43512
  return;
@@ -43047,13 +43557,13 @@ function SlideshowDataGridRenderer({
43047
43557
  () => useLocalGrid && gridModule ? new gridModule.TableStoreClient({ headers: previewHeaders, cells: previewRows }) : null,
43048
43558
  [previewHeaders, previewRows, useLocalGrid, gridModule]
43049
43559
  );
43050
- useEffect70(() => () => localProvider?.dispose(), [localProvider]);
43560
+ useEffect71(() => () => localProvider?.dispose(), [localProvider]);
43051
43561
  const ready = currentRemote?.status === "ready" ? currentRemote : void 0;
43052
43562
  const provider = ready?.provider ?? localProvider;
43053
43563
  const ingest = ready?.ingest ?? (localProvider ? { headers: previewHeaders, cells: previewRows } : void 0);
43054
43564
  const initialView = ready?.initialView ?? EMPTY_VIEW;
43055
43565
  const [view, setView] = useState79(initialView);
43056
- useEffect70(() => setView(initialView), [initialView, provider]);
43566
+ useEffect71(() => setView(initialView), [initialView, provider]);
43057
43567
  if (!provider || !ingest || !gridModule) return /* @__PURE__ */ jsx90(Fragment29, { children: fallback });
43058
43568
  const { style } = layer.content;
43059
43569
  const rowHeight = Math.max(32, style.fontSize * 2.1);
@@ -43191,11 +43701,11 @@ function PreviewPanel({
43191
43701
  const isDashboardMode = activeDisplayMode === "dashboard";
43192
43702
  const isFlashcardsMode = activeDisplayMode === "flashcards";
43193
43703
  const presentationProblem = !printMode?.active && !isDocumentMode && !isNarrateMode && !isPageMode && !isFlashcardsMode && !isDashboardMode ? previewDoc?.diagnostics?.find((diagnostic) => diagnostic.code === "presentation-invalid")?.message : void 0;
43194
- useEffect71(() => {
43704
+ useEffect72(() => {
43195
43705
  if (presentation?.activeTarget === "window") return;
43196
43706
  setPlaybackState(null);
43197
43707
  }, [presentation?.activeTarget]);
43198
- useEffect71(() => {
43708
+ useEffect72(() => {
43199
43709
  if (presentation?.activeTarget !== "window" || !presentation.popupRoot) return;
43200
43710
  const mainRoot = mainSurfaceRef.current;
43201
43711
  const followerRoot = popupSurfaceRef.current;
@@ -43478,12 +43988,12 @@ function PreviewPanel({
43478
43988
  }
43479
43989
 
43480
43990
  // src/MediaBin.tsx
43481
- import { useState as useState82, useEffect as useEffect73, useRef as useRef66, useCallback as useCallback55 } from "react";
43991
+ import { useState as useState82, useEffect as useEffect74, useRef as useRef66, useCallback as useCallback55 } from "react";
43482
43992
  import { isSvgImageSource as isSvgImageSource3 } from "@bendyline/squisq/markdown";
43483
43993
  import { sanitizedSvgImageUrl as sanitizedSvgImageUrl2 } from "@bendyline/squisq-react";
43484
43994
 
43485
43995
  // src/MediaPreviewDialog.tsx
43486
- import { useEffect as useEffect72, useId as useId16, useRef as useRef65, useState as useState81 } from "react";
43996
+ import { useEffect as useEffect73, useId as useId16, useRef as useRef65, useState as useState81 } from "react";
43487
43997
  import { jsx as jsx92, jsxs as jsxs72 } from "react/jsx-runtime";
43488
43998
  function MediaPreviewDialog({
43489
43999
  kind,
@@ -43499,7 +44009,7 @@ function MediaPreviewDialog({
43499
44009
  const titleId = useId16();
43500
44010
  const [playbackFailed, setPlaybackFailed] = useState81(false);
43501
44011
  useModalDialog({ rootRef: overlayRef, dialogRef, onClose });
43502
- useEffect72(() => setPlaybackFailed(false), [src]);
44012
+ useEffect73(() => setPlaybackFailed(false), [src]);
43503
44013
  let body;
43504
44014
  if (failed || playbackFailed || src === "") {
43505
44015
  body = /* @__PURE__ */ jsx92("div", { className: "squisq-media-preview-message", children: "This file can\u2019t be previewed." });
@@ -43643,7 +44153,7 @@ function MediaBin({
43643
44153
  }) {
43644
44154
  const [entries, setEntries] = useState82([]);
43645
44155
  const listRef = useRef66(null);
43646
- useEffect73(() => {
44156
+ useEffect74(() => {
43647
44157
  if (!highlightPath || !listRef.current) return;
43648
44158
  const item = listRef.current.querySelector(`[data-media-path="${CSS.escape(highlightPath)}"]`);
43649
44159
  if (item instanceof HTMLElement) {
@@ -43677,7 +44187,7 @@ function MediaBin({
43677
44187
  },
43678
44188
  [onCountChange]
43679
44189
  );
43680
- useEffect73(() => {
44190
+ useEffect74(() => {
43681
44191
  if (!contextMenu) return;
43682
44192
  const handlePointerDown = (event) => {
43683
44193
  if (contextMenuRef.current?.contains(event.target)) return;
@@ -43698,7 +44208,7 @@ function MediaBin({
43698
44208
  window.removeEventListener("resize", close);
43699
44209
  };
43700
44210
  }, [contextMenu]);
43701
- useEffect73(() => {
44211
+ useEffect74(() => {
43702
44212
  if (!mediaProvider) {
43703
44213
  setEntries([]);
43704
44214
  setThumbUrls({});
@@ -43855,7 +44365,7 @@ function MediaBin({
43855
44365
  const openContextMenu = useCallback55((entry, x, y) => {
43856
44366
  setContextMenu({ entry, x, y });
43857
44367
  }, []);
43858
- useEffect73(() => {
44368
+ useEffect74(() => {
43859
44369
  setPreview(null);
43860
44370
  }, [mediaProvider]);
43861
44371
  const openPreview = useCallback55(
@@ -44195,7 +44705,7 @@ function DropZone({
44195
44705
  }
44196
44706
 
44197
44707
  // src/Tooltip.tsx
44198
- import { useEffect as useEffect74, useLayoutEffect as useLayoutEffect16, useRef as useRef67, useState as useState84 } from "react";
44708
+ import { useEffect as useEffect75, useLayoutEffect as useLayoutEffect18, useRef as useRef67, useState as useState84 } from "react";
44199
44709
  import { createPortal as createPortal19 } from "react-dom";
44200
44710
 
44201
44711
  // src/tooltipPlacement.ts
@@ -44218,7 +44728,7 @@ function TooltipLayer() {
44218
44728
  const visibleRef = useRef67(false);
44219
44729
  const pressedTargetRef = useRef67(null);
44220
44730
  const lastPointerWasTouchRef = useRef67(false);
44221
- useLayoutEffect16(() => {
44731
+ useLayoutEffect18(() => {
44222
44732
  if (!state) return;
44223
44733
  const node2 = tooltipRef.current;
44224
44734
  if (!node2) return;
@@ -44229,7 +44739,7 @@ function TooltipLayer() {
44229
44739
  node2.style.left = `${left}px`;
44230
44740
  node2.style.visibility = "visible";
44231
44741
  }, [state]);
44232
- useEffect74(() => {
44742
+ useEffect75(() => {
44233
44743
  const clearTimer = () => {
44234
44744
  if (timerRef.current) {
44235
44745
  clearTimeout(timerRef.current);
@@ -44331,7 +44841,7 @@ function TooltipLayer() {
44331
44841
  }
44332
44842
 
44333
44843
  // src/EditorShell.tsx
44334
- import { useEffect as useEffect81, useRef as useRef76, useState as useState91, useCallback as useCallback63, useMemo as useMemo62 } from "react";
44844
+ import { useEffect as useEffect82, useRef as useRef76, useState as useState91, useCallback as useCallback63, useMemo as useMemo62 } from "react";
44335
44845
 
44336
44846
  // src/BlockPreviewPanel.tsx
44337
44847
  import { useMemo as useMemo58 } from "react";
@@ -44375,7 +44885,7 @@ function BlockPreviewPanel({ basePath = "/" }) {
44375
44885
  }
44376
44886
 
44377
44887
  // src/NavigationSheet.tsx
44378
- import { useCallback as useCallback57, useEffect as useEffect76, useId as useId17, useRef as useRef70, useState as useState85 } from "react";
44888
+ import { useCallback as useCallback57, useEffect as useEffect77, useId as useId17, useRef as useRef70, useState as useState85 } from "react";
44379
44889
 
44380
44890
  // src/BlockPreviewList.tsx
44381
44891
  import { useCallback as useCallback56, useRef as useRef68 } from "react";
@@ -44459,7 +44969,7 @@ function BlockPreviewList({
44459
44969
  }
44460
44970
 
44461
44971
  // src/useNavigationSheetSwipe.ts
44462
- import { useEffect as useEffect75, useRef as useRef69 } from "react";
44972
+ import { useEffect as useEffect76, useRef as useRef69 } from "react";
44463
44973
  var NAVIGATION_SHEET_MAX_BODY_WIDTH = 720;
44464
44974
  var EDGE_ZONE_PX = 24;
44465
44975
  var AXIS_LOCK_PX = 10;
@@ -44476,7 +44986,7 @@ function useNavigationSheetSwipe({
44476
44986
  }) {
44477
44987
  const callbacksRef = useRef69({ onDragStart, onOpen, onClose });
44478
44988
  callbacksRef.current = { onDragStart, onOpen, onClose };
44479
- useEffect75(() => {
44989
+ useEffect76(() => {
44480
44990
  const sheet = sheetRef.current;
44481
44991
  const surface = sheet?.parentElement;
44482
44992
  if (!sheet || !surface) return;
@@ -44610,7 +45120,7 @@ function NavigationSheet({
44610
45120
  const [lastShown, setLastShown] = useState85(shown);
44611
45121
  if (shown && shown !== lastShown) setLastShown(shown);
44612
45122
  const content = shown ?? lastShown;
44613
- useEffect76(() => {
45123
+ useEffect77(() => {
44614
45124
  if (shown || !lastShown) return;
44615
45125
  const timer = setTimeout(() => setLastShown(null), CLOSE_LINGER_MS);
44616
45126
  return () => clearTimeout(timer);
@@ -44635,14 +45145,14 @@ function NavigationSheet({
44635
45145
  onClose: close
44636
45146
  });
44637
45147
  useEscapeDismissal(open, close);
44638
- useEffect76(() => {
45148
+ useEffect77(() => {
44639
45149
  const scrim = scrimRef.current;
44640
45150
  if (!scrim) return;
44641
45151
  const stop = (event) => event.stopPropagation();
44642
45152
  scrim.addEventListener("pointerdown", stop);
44643
45153
  return () => scrim.removeEventListener("pointerdown", stop);
44644
45154
  }, []);
44645
- useEffect76(() => {
45155
+ useEffect77(() => {
44646
45156
  const sheet = sheetRef.current;
44647
45157
  const surface = sheet?.parentElement;
44648
45158
  if (!open || !sheet || !surface) return;
@@ -44724,6 +45234,35 @@ function NavigationSheet({
44724
45234
  );
44725
45235
  }
44726
45236
 
45237
+ // src/linkHoverTitle.ts
45238
+ var OWNED = "data-squisq-link-title";
45239
+ function titleHost(anchor) {
45240
+ return anchor.closest(".ProseMirror") ?? anchor;
45241
+ }
45242
+ function clear(host) {
45243
+ host.removeAttribute("title");
45244
+ host.removeAttribute(OWNED);
45245
+ }
45246
+ function showLinkHoverTitle(anchor) {
45247
+ const href = anchor.getAttribute("href");
45248
+ if (!href) return;
45249
+ const host = titleHost(anchor);
45250
+ if (host !== anchor && anchor.hasAttribute("title")) return;
45251
+ if (host.hasAttribute("title")) {
45252
+ if (!host.hasAttribute(OWNED) || host.getAttribute("title") === href) return;
45253
+ }
45254
+ host.setAttribute("title", href);
45255
+ host.setAttribute(OWNED, "true");
45256
+ }
45257
+ function clearLinkHoverTitle(anchor) {
45258
+ const host = titleHost(anchor);
45259
+ if (host.hasAttribute(OWNED)) clear(host);
45260
+ }
45261
+ function clearStaleLinkHoverTitle(target) {
45262
+ const host = target.closest?.(`.ProseMirror[${OWNED}]`);
45263
+ if (host) clear(host);
45264
+ }
45265
+
44727
45266
  // src/proofing/ProofingPanel.tsx
44728
45267
  import { jsx as jsx99, jsxs as jsxs77 } from "react/jsx-runtime";
44729
45268
  var CATEGORY_LABELS = {
@@ -44811,7 +45350,7 @@ function ProofingPanel() {
44811
45350
  }
44812
45351
 
44813
45352
  // src/imageEditor/ImageEditModal.tsx
44814
- import { useCallback as useCallback58, useEffect as useEffect77, useMemo as useMemo59, useRef as useRef71, useState as useState86 } from "react";
45353
+ import { useCallback as useCallback58, useEffect as useEffect78, useMemo as useMemo59, useRef as useRef71, useState as useState86 } from "react";
44815
45354
  import { DARK_SURFACE, LIGHT_SURFACE } from "@bendyline/squisq/schemas";
44816
45355
  import {
44817
45356
  MemoryContentContainer,
@@ -44846,7 +45385,7 @@ function ImageEditModal({
44846
45385
  }, [container, relativePath]);
44847
45386
  const [initialSrc, setInitialSrc] = useState86(null);
44848
45387
  const [resolveError, setResolveError] = useState86(null);
44849
- useEffect77(() => {
45388
+ useEffect78(() => {
44850
45389
  let cancelled = false;
44851
45390
  setInitialSrc(null);
44852
45391
  setResolveError(null);
@@ -44968,7 +45507,7 @@ function MediaEditContextMenuItems() {
44968
45507
  // src/mediaEdit/MediaEditModal.tsx
44969
45508
  import {
44970
45509
  useCallback as useCallback62,
44971
- useEffect as useEffect80,
45510
+ useEffect as useEffect81,
44972
45511
  useId as useId18,
44973
45512
  useMemo as useMemo61,
44974
45513
  useRef as useRef75,
@@ -44979,7 +45518,7 @@ import {
44979
45518
  } from "@bendyline/squisq-video-react/media-edit";
44980
45519
 
44981
45520
  // src/mediaEdit/MediaEditAudioSection.tsx
44982
- import { useCallback as useCallback59, useEffect as useEffect78, useRef as useRef72, useState as useState87 } from "react";
45521
+ import { useCallback as useCallback59, useEffect as useEffect79, useRef as useRef72, useState as useState87 } from "react";
44983
45522
  import { MEDIA_FX_SPECS as MEDIA_FX_SPECS2, serializeMediaFx } from "@bendyline/squisq/mediaEdit";
44984
45523
 
44985
45524
  // src/mediaEdit/mediaEditDraft.ts
@@ -45133,7 +45672,7 @@ var LOUDNESS_PRESETS = [
45133
45672
  var PREVIEW_SECONDS = 10;
45134
45673
  function useMediaDuration(url) {
45135
45674
  const [duration, setDuration] = useState87(null);
45136
- useEffect78(() => {
45675
+ useEffect79(() => {
45137
45676
  setDuration(null);
45138
45677
  if (!url || typeof Audio === "undefined") return;
45139
45678
  const probe = new Audio();
@@ -45173,8 +45712,8 @@ function usePreviewPlayer(preview) {
45173
45712
  setPosition(0);
45174
45713
  setPlaying(null);
45175
45714
  }, []);
45176
- useEffect78(() => stop, [preview, stop]);
45177
- useEffect78(
45715
+ useEffect79(() => stop, [preview, stop]);
45716
+ useEffect79(
45178
45717
  () => () => {
45179
45718
  void ctxRef.current?.close();
45180
45719
  ctxRef.current = null;
@@ -45213,7 +45752,7 @@ function usePreviewPlayer(preview) {
45213
45752
  },
45214
45753
  [duration, playing, play]
45215
45754
  );
45216
- useEffect78(() => {
45755
+ useEffect79(() => {
45217
45756
  if (!playing) return;
45218
45757
  let frame = requestAnimationFrame(function tick() {
45219
45758
  setPosition(currentOffset());
@@ -45268,7 +45807,7 @@ function MediaEditAudioSection({
45268
45807
  const [previewError, setPreviewError] = useState87(null);
45269
45808
  const previewAbort = useRef72(null);
45270
45809
  const player = usePreviewPlayer(preview);
45271
- useEffect78(() => () => previewAbort.current?.abort(), []);
45810
+ useEffect79(() => () => previewAbort.current?.abort(), []);
45272
45811
  const runPreview = useCallback59(async () => {
45273
45812
  if (draftFx.ops.length === 0) return;
45274
45813
  previewAbort.current?.abort();
@@ -45448,7 +45987,7 @@ function MediaEditAudioSection({
45448
45987
  }
45449
45988
 
45450
45989
  // src/mediaEdit/MediaEditTimingSection.tsx
45451
- import { useCallback as useCallback60, useEffect as useEffect79, useRef as useRef73, useState as useState88 } from "react";
45990
+ import { useCallback as useCallback60, useEffect as useEffect80, useRef as useRef73, useState as useState88 } from "react";
45452
45991
  import { jsx as jsx102, jsxs as jsxs80 } from "react/jsx-runtime";
45453
45992
  var DEFAULT_MAX_PAUSE = 0.8;
45454
45993
  function formatDb(value) {
@@ -45466,7 +46005,7 @@ function MediaEditTimingSection({
45466
46005
  const [finding, setFinding] = useState88(false);
45467
46006
  const [error, setError] = useState88(null);
45468
46007
  const abortRef = useRef73(null);
45469
- useEffect79(() => () => abortRef.current?.abort(), []);
46008
+ useEffect80(() => () => abortRef.current?.abort(), []);
45470
46009
  const findPauses = useCallback60(async () => {
45471
46010
  abortRef.current?.abort();
45472
46011
  const controller = new AbortController();
@@ -45795,7 +46334,7 @@ function MediaEditModal({ target, onClose }) {
45795
46334
  const [draft, setDraft] = useState90(() => draftFromEdits(item?.edits));
45796
46335
  const seedKey = item ? `${item.sourceLine}|${editsSignature(item.edits)}` : null;
45797
46336
  const seededRef = useRef75(null);
45798
- useEffect80(() => {
46337
+ useEffect81(() => {
45799
46338
  if (seedKey === null || seededRef.current === seedKey) return;
45800
46339
  seededRef.current = seedKey;
45801
46340
  setDraft(draftFromEdits(item?.edits));
@@ -46331,7 +46870,7 @@ function EditorShell({
46331
46870
  () => mediaEditRenders === void 0 && effectiveMediaProvider ? createMediaEditRenderManager({ mediaProvider: effectiveMediaProvider }) : null,
46332
46871
  [mediaEditRenders, effectiveMediaProvider]
46333
46872
  );
46334
- useEffect81(() => () => ownedMediaEditRenders?.dispose(), [ownedMediaEditRenders]);
46873
+ useEffect82(() => () => ownedMediaEditRenders?.dispose(), [ownedMediaEditRenders]);
46335
46874
  const effectiveMediaEditRenders = mediaEditRenders === void 0 ? ownedMediaEditRenders : mediaEditRenders;
46336
46875
  const filesToggleEnabled = showFilesToggle ?? effectiveMediaProvider !== void 0;
46337
46876
  const effectiveInitialView = hostMode === "chat" || !showPlayTab && initialView === "preview" ? "wysiwyg" : initialView;
@@ -46381,7 +46920,7 @@ function EditorShell({
46381
46920
  themeInheritance,
46382
46921
  viewPreferences,
46383
46922
  onViewPreferencesChange,
46384
- children: /* @__PURE__ */ jsx105(ProofingRoot, { children: /* @__PURE__ */ jsx105(ReviewRoot, { providers: reviewProviders, children: /* @__PURE__ */ jsx105(PageTemplateContext.Provider, { value: pageTemplates, children: /* @__PURE__ */ jsx105(
46923
+ children: /* @__PURE__ */ jsx105(ProofingRoot, { onLinkClick, children: /* @__PURE__ */ jsx105(ReviewRoot, { providers: reviewProviders, children: /* @__PURE__ */ jsx105(PageTemplateContext.Provider, { value: pageTemplates, children: /* @__PURE__ */ jsx105(
46385
46924
  EditorShellInner,
46386
46925
  {
46387
46926
  ariaLabel: ariaLabel ?? (hostMode === "chat" ? "Message" : fileName ? `Edit ${fileName}` : "Document editor"),
@@ -46566,7 +47105,7 @@ function EditorShellInner({
46566
47105
  const processedAudio = useProcessedAudio4(mediaEditRenders);
46567
47106
  const { docThemes, onDocThemesChange } = useDocCustomThemes();
46568
47107
  const lastEditingView = useRef76(activeView === "raw" ? "raw" : "wysiwyg");
46569
- useEffect81(() => {
47108
+ useEffect82(() => {
46570
47109
  if (activeView !== "preview") lastEditingView.current = activeView;
46571
47110
  }, [activeView]);
46572
47111
  const editingView = activeView === "preview" ? lastEditingView.current : activeView;
@@ -46632,13 +47171,13 @@ function EditorShellInner({
46632
47171
  }
46633
47172
  const imageEditFallbackContainer = imageEditFallbackContainerRef.current;
46634
47173
  const isDark = colorScheme === "dark";
46635
- useEffect81(() => {
47174
+ useEffect82(() => {
46636
47175
  if (!isTimelineMode || !hasTimelineVideo) setTimelineVideoVisible(false);
46637
47176
  }, [isTimelineMode, hasTimelineVideo]);
46638
- useEffect81(() => {
47177
+ useEffect82(() => {
46639
47178
  if (!isTimelineMode || !doc?.blocks.length) setTimelineCompositionVisible(false);
46640
47179
  }, [isTimelineMode, doc]);
46641
- useEffect81(() => {
47180
+ useEffect82(() => {
46642
47181
  if (!mediaProvider) {
46643
47182
  setMediaCount(0);
46644
47183
  return;
@@ -46662,7 +47201,7 @@ function EditorShellInner({
46662
47201
  cancelled = true;
46663
47202
  };
46664
47203
  }, [mediaProvider, mediaListRefreshKey]);
46665
- useEffect81(() => {
47204
+ useEffect82(() => {
46666
47205
  if (showFilesWhenNotEmpty && mediaCount > 0) setShowFiles(true);
46667
47206
  }, [mediaCount, showFilesWhenNotEmpty]);
46668
47207
  const handleToggleFiles = useCallback63(() => {
@@ -46682,7 +47221,7 @@ function EditorShellInner({
46682
47221
  [insertionTarget]
46683
47222
  );
46684
47223
  const docBasenameRef = useRef76("document");
46685
- useEffect81(() => {
47224
+ useEffect82(() => {
46686
47225
  let cancelled = false;
46687
47226
  docBasenameRef.current = "document";
46688
47227
  void resolveDocBasename(workspaceContainer).then((base) => {
@@ -46765,7 +47304,7 @@ function EditorShellInner({
46765
47304
  onDrop: handleFileDrop,
46766
47305
  enabled: !readOnly
46767
47306
  });
46768
- useEffect81(() => {
47307
+ useEffect82(() => {
46769
47308
  onChange?.(markdownSource);
46770
47309
  }, [markdownSource, onChange]);
46771
47310
  const handleShellKeyDown = useCallback63((e2) => {
@@ -46792,6 +47331,7 @@ function EditorShellInner({
46792
47331
  if (!onLinkClick || event.button !== 0) return;
46793
47332
  const anchor = event.target.closest?.("a[href]");
46794
47333
  if (!anchor || !event.currentTarget.contains(anchor)) return;
47334
+ if (proofingTapTarget(event.nativeEvent)) return;
46795
47335
  const href = anchor.getAttribute("href");
46796
47336
  if (!href || onLinkClick(href) === false) return;
46797
47337
  event.preventDefault();
@@ -46802,23 +47342,21 @@ function EditorShellInner({
46802
47342
  const handleShellLinkMouseOver = useCallback63(
46803
47343
  (event) => {
46804
47344
  if (!onLinkClick) return;
46805
- const anchor = event.target.closest?.("a[href]");
46806
- if (!anchor || !event.currentTarget.contains(anchor)) return;
46807
- const href = anchor.getAttribute("href");
46808
- if (!href || anchor.hasAttribute("title") && !anchor.hasAttribute("data-squisq-link-title")) {
47345
+ const target = event.target;
47346
+ const anchor = target.closest?.("a[href]");
47347
+ if (!anchor || !event.currentTarget.contains(anchor)) {
47348
+ clearStaleLinkHoverTitle(target);
46809
47349
  return;
46810
47350
  }
46811
- anchor.setAttribute("title", href);
46812
- anchor.setAttribute("data-squisq-link-title", "true");
47351
+ showLinkHoverTitle(anchor);
46813
47352
  },
46814
47353
  [onLinkClick]
46815
47354
  );
46816
47355
  const handleShellLinkMouseOut = useCallback63((event) => {
46817
- const anchor = event.target.closest?.("a[data-squisq-link-title]");
47356
+ const anchor = event.target.closest?.("a[href]");
46818
47357
  if (!anchor || !event.currentTarget.contains(anchor)) return;
46819
47358
  if (event.relatedTarget instanceof Node && anchor.contains(event.relatedTarget)) return;
46820
- anchor.removeAttribute("title");
46821
- anchor.removeAttribute("data-squisq-link-title");
47359
+ clearLinkHoverTitle(anchor);
46822
47360
  }, []);
46823
47361
  const autoGrow = minHeight !== void 0 || maxHeight !== void 0;
46824
47362
  const shell = /* @__PURE__ */ jsxs83(