@linloir/limr 1.0.0 → 1.0.1

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.
Files changed (64) hide show
  1. package/README.en.md +36 -8
  2. package/README.md +36 -8
  3. package/benchmarks/results/layout.json +2593 -0
  4. package/benchmarks/results/markstream-code.json +1823 -0
  5. package/benchmarks/results/markstream-reading-repeat.json +1152 -0
  6. package/benchmarks/results/markstream.json +12486 -0
  7. package/benchmarks/results/streaming-browser.json +77 -0
  8. package/benchmarks/results/streaming.json +357 -0
  9. package/dist/base.css +83 -3
  10. package/dist/{chunk-TTISGBST.js → chunk-BG5QUQFR.js} +327 -72
  11. package/dist/chunk-BG5QUQFR.js.map +1 -0
  12. package/dist/chunk-BVHHOVMT.js +179 -0
  13. package/dist/chunk-BVHHOVMT.js.map +1 -0
  14. package/dist/chunk-FJZCQY2S.js +155 -0
  15. package/dist/chunk-FJZCQY2S.js.map +1 -0
  16. package/dist/chunk-FSI5OTBW.js +1 -0
  17. package/dist/{chunk-N77VD2YR.js → chunk-GU2RPESF.js} +145 -83
  18. package/dist/chunk-GU2RPESF.js.map +1 -0
  19. package/dist/{chunk-3ILUZKDB.js → chunk-SUAAUGAX.js} +1280 -501
  20. package/dist/chunk-SUAAUGAX.js.map +1 -0
  21. package/dist/core/index.d.ts +2 -2
  22. package/dist/core/index.js +13 -5
  23. package/dist/extensions/index.d.ts +30 -5
  24. package/dist/extensions/index.js +15 -3
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.js +27 -8
  27. package/dist/{engine-43u8B4yt.d.ts → knuth-plass-C8mgFm7W.d.ts} +67 -1
  28. package/dist/knuth-plass-layout-CHQCWKB5.js +449 -0
  29. package/dist/knuth-plass-layout-CHQCWKB5.js.map +1 -0
  30. package/dist/react/index.d.ts +5 -4
  31. package/dist/react/index.js +5 -3
  32. package/dist/styles.css +83 -3
  33. package/dist/{translations-DHnYMg4h.d.ts → translations-BXzSwLBJ.d.ts} +39 -2
  34. package/docs/en/README.md +8 -6
  35. package/docs/en/annotations.md +3 -1
  36. package/docs/en/api.md +9 -1
  37. package/docs/en/architecture.md +11 -3
  38. package/docs/en/extensions.md +53 -4
  39. package/docs/en/getting-started.md +2 -0
  40. package/docs/en/integration.md +26 -2
  41. package/docs/en/modules.md +9 -5
  42. package/docs/en/performance.md +42 -2
  43. package/docs/en/styles.md +21 -2
  44. package/docs/en/syntax.md +118 -15
  45. package/docs/zh-CN/README.md +8 -6
  46. package/docs/zh-CN/annotations.md +3 -1
  47. package/docs/zh-CN/api.md +9 -1
  48. package/docs/zh-CN/architecture.md +11 -3
  49. package/docs/zh-CN/extensions.md +55 -4
  50. package/docs/zh-CN/getting-started.md +2 -0
  51. package/docs/zh-CN/integration.md +26 -2
  52. package/docs/zh-CN/modules.md +9 -5
  53. package/docs/zh-CN/performance.md +42 -2
  54. package/docs/zh-CN/styles.md +21 -2
  55. package/docs/zh-CN/syntax.md +120 -15
  56. package/package.json +9 -1
  57. package/skills/limr-plugin-development/references/contract.md +9 -3
  58. package/skills/limr-use/references/integration.md +11 -3
  59. package/skills/limr-use/references/syntax.md +8 -5
  60. package/dist/chunk-3ILUZKDB.js.map +0 -1
  61. package/dist/chunk-N77VD2YR.js.map +0 -1
  62. package/dist/chunk-TTISGBST.js.map +0 -1
  63. package/dist/chunk-V2OG4Y3F.js +0 -1
  64. /package/dist/{chunk-V2OG4Y3F.js.map → chunk-FSI5OTBW.js.map} +0 -0
@@ -9,7 +9,6 @@ import {
9
9
  highlightPalette,
10
10
  highlightSelection,
11
11
  scrollWithMotion,
12
- sourceRangeStartRect,
13
12
  useAppearance,
14
13
  useDocument,
15
14
  useDocumentSelector,
@@ -19,20 +18,26 @@ import {
19
18
  useSelection,
20
19
  useStoreSelector,
21
20
  useTheme
22
- } from "./chunk-3ILUZKDB.js";
21
+ } from "./chunk-SUAAUGAX.js";
23
22
  import {
24
23
  annotationReferenceRange,
25
24
  defaultHighlight,
26
- intersects,
27
25
  mapText,
28
- rangeOf,
29
26
  resolveAnnotationAnchors,
30
27
  sameRange,
31
28
  selectionFromRange
32
- } from "./chunk-TTISGBST.js";
29
+ } from "./chunk-BG5QUQFR.js";
30
+ import {
31
+ atomicSelectionText,
32
+ sourceRangeStartRect
33
+ } from "./chunk-BVHHOVMT.js";
34
+ import {
35
+ intersects,
36
+ rangeOf
37
+ } from "./chunk-FJZCQY2S.js";
33
38
 
34
39
  // src/components/Reader.tsx
35
- import { useEffect as useEffect7, useId as useId2, useRef as useRef5, useState as useState5 } from "react";
40
+ import { useEffect as useEffect7, useId as useId2, useRef as useRef6, useState as useState6 } from "react";
36
41
 
37
42
  // src/components/TableOfContents.tsx
38
43
  import { useEffect as useEffect2, useRef } from "react";
@@ -43,7 +48,8 @@ function useOverflowFade(ref, runtime, contentSelector, beforeMeasure) {
43
48
  useEffect(() => {
44
49
  const host = ref.current;
45
50
  if (!host) return;
46
- let frame = 0, transitions = 0;
51
+ let frame = 0;
52
+ const transitions = /* @__PURE__ */ new Map();
47
53
  const measure = () => {
48
54
  frame = 0;
49
55
  beforeMeasure?.();
@@ -68,8 +74,12 @@ function useOverflowFade(ref, runtime, contentSelector, beforeMeasure) {
68
74
  };
69
75
  set("--limr-fade-start", top - box.top);
70
76
  set("--limr-fade-end", bottom - box.top);
71
- if (transitions && host.getAnimations({ subtree: true }).some((animation) => animation.playState === "running")) schedule();
72
- else transitions = 0;
77
+ const now = performance.now();
78
+ for (const [target, properties] of transitions) {
79
+ for (const [property, end2] of properties) if (end2 <= now || !host.contains(target)) properties.delete(property);
80
+ if (!properties.size) transitions.delete(target);
81
+ }
82
+ if (transitions.size) schedule();
73
83
  };
74
84
  const schedule = () => {
75
85
  if (!frame) frame = requestAnimationFrame(measure);
@@ -78,12 +88,23 @@ function useOverflowFade(ref, runtime, contentSelector, beforeMeasure) {
78
88
  cancelAnimationFrame(frame);
79
89
  measure();
80
90
  };
81
- const start = () => {
82
- transitions++;
91
+ const start = (event) => {
92
+ const target = event.target;
93
+ if (!(target instanceof Element) || !/^(top|bottom|left|right|height|max-height|min-height|width|max-width|transform|translate|grid-template-rows|padding.*|margin.*)$/.test(event.propertyName)) return;
94
+ const css = getComputedStyle(target), properties = css.transitionProperty.split(",").map((value) => value.trim());
95
+ const index = Math.max(0, properties.indexOf(event.propertyName));
96
+ const milliseconds = (value) => {
97
+ const list = value.split(",");
98
+ const item = list[index % list.length].trim();
99
+ return parseFloat(item) * (item.endsWith("ms") ? 1 : 1e3);
100
+ };
101
+ const pending = transitions.get(target) ?? /* @__PURE__ */ new Map();
102
+ pending.set(event.propertyName, performance.now() + milliseconds(css.transitionDuration) + milliseconds(css.transitionDelay) + 34);
103
+ transitions.set(target, pending);
83
104
  schedule();
84
105
  };
85
- const end = () => {
86
- transitions = Math.max(0, transitions - 1);
106
+ const end = (event) => {
107
+ if (event.target instanceof Element) transitions.get(event.target)?.delete(event.propertyName);
87
108
  schedule();
88
109
  };
89
110
  host.addEventListener("transitionrun", start);
@@ -95,7 +116,10 @@ function useOverflowFade(ref, runtime, contentSelector, beforeMeasure) {
95
116
  if (records.some((record) => {
96
117
  const element = record.target instanceof Element ? record.target : record.target.parentElement;
97
118
  return element && element !== host && !element.closest(".limr-highlight-layer,.limr-paint-layer");
98
- })) refresh();
119
+ })) {
120
+ if (beforeMeasure) refresh();
121
+ else schedule();
122
+ }
99
123
  });
100
124
  mutation.observe(host, { subtree: true, childList: true, characterData: true, attributes: true, attributeFilter: ["style", "class"] });
101
125
  const unsubscribe = [runtime.layout.subscribe(schedule), runtime.viewport.subscribe(refresh), runtime.documentOffset.subscribe(schedule), runtime.configuration.subscribe(schedule)];
@@ -187,7 +211,7 @@ function TableOfContents({ title, maxDepth = 6, followActive = true, topInset =
187
211
  }
188
212
 
189
213
  // src/components/AnnotationSidebar.tsx
190
- import { useCallback, useEffect as useEffect4, useMemo, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
214
+ import { memo, useCallback, useEffect as useEffect4, useMemo, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
191
215
  import { flushSync } from "react-dom";
192
216
 
193
217
  // src/components/AnnotationContent.tsx
@@ -273,6 +297,19 @@ var alignSidebar = (items, { activeId, gap, height, prioritizeActive }) => {
273
297
  }
274
298
  return new Map(items.map((item, index) => [item.id, positions[index]]));
275
299
  };
300
+ var SidebarCardBody = memo(function SidebarCardBody2({ occurrence, annotation, annotations, reference, active, title, toggleLabel, Preview, activate }) {
301
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
302
+ reference && /* @__PURE__ */ jsx3("div", { className: "limr-sidebar-reference", title: reference, children: /* @__PURE__ */ jsx3("p", { className: "limr-sidebar-excerpt", children: reference }) }),
303
+ /* @__PURE__ */ jsxs3("div", { className: "limr-sidebar-card-body", children: [
304
+ /* @__PURE__ */ jsxs3("button", { type: "button", className: "limr-sidebar-card-heading", onClick: () => activate(occurrence.id), "aria-expanded": active, children: [
305
+ title,
306
+ /* @__PURE__ */ jsx3("span", { className: "limr-sidebar-toggle", children: /* @__PURE__ */ jsx3("span", { className: "limr-button-label limr-underline", children: toggleLabel }, String(active)) })
307
+ ] }),
308
+ Preview && annotation && /* @__PURE__ */ jsx3(Collapse, { open: !active, keepMounted: true, className: "limr-sidebar-preview-panel", children: /* @__PURE__ */ jsx3("div", { className: "limr-sidebar-preview", children: /* @__PURE__ */ jsx3(Preview, { annotation, data: annotation.data, occurrence, annotations, referenceText: reference }) }) }),
309
+ /* @__PURE__ */ jsx3(Collapse, { open: active, className: "limr-sidebar-expansion", children: /* @__PURE__ */ jsx3("div", { className: "limr-sidebar-card-content", children: /* @__PURE__ */ jsx3(AnnotationGroup, { occurrence, presentation: "sidebar", enabled: active }) }) })
310
+ ] })
311
+ ] });
312
+ });
276
313
  function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSidebar, renderCard, className, style }) {
277
314
  const runtime = useRuntime();
278
315
  const document2 = useDocument();
@@ -452,7 +489,7 @@ function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSide
452
489
  useIsomorphicLayoutEffect(() => {
453
490
  if (sync === "document") runtime.documentOffset.set(displacement);
454
491
  }, [runtime, displacement, sync]);
455
- const lockCard = (id) => {
492
+ const lockCard = useCallback((id) => {
456
493
  if (locked?.id === id) return;
457
494
  const card = cards.current.get(id);
458
495
  if (card && rail.current) {
@@ -460,7 +497,14 @@ function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSide
460
497
  const top = card.getBoundingClientRect().top - origin;
461
498
  setCardLock({ id, top });
462
499
  }
463
- };
500
+ }, [locked?.id]);
501
+ const activateCard = useCallback((id) => {
502
+ const state = runtime.interaction.get();
503
+ const hovering = state.hovered === id || cards.current.get(id)?.matches(":hover");
504
+ lockCard(id);
505
+ runtime.activate(state.active === id ? null : id, "sidebar");
506
+ if (hovering) runtime.hover(id, "sidebar");
507
+ }, [runtime, lockCard]);
464
508
  return /* @__PURE__ */ jsx3("aside", { ref: rail, "aria-label": locale.annotations, className: `limr-sidebar ${className ?? ""}`, "data-sync": sync, "data-releasing": releasing ? "" : void 0, "data-limr-theme": theme, "data-limr-appearance": appearance, style, children: items.map((occurrence) => {
465
509
  const annotation = decorations.annotations.get(occurrence.annotationIds[0]);
466
510
  const definition = runtime.annotationTypes.get(annotation?.type ?? "citation");
@@ -485,12 +529,7 @@ function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSide
485
529
  } : { marginBlockEnd: gap }
486
530
  };
487
531
  const collapseOnContentClick = occurrence.annotationIds.every((id) => runtime.annotationTypes.get(decorations.annotations.get(id)?.type ?? "")?.sidebar?.collapseOnContentClick);
488
- const activate = () => {
489
- const hovering = runtime.interaction.get().hovered === occurrence.id || cards.current.get(occurrence.id)?.matches(":hover");
490
- lockCard(occurrence.id);
491
- runtime.activate(isActive ? null : occurrence.id, "sidebar");
492
- if (hovering) runtime.hover(occurrence.id, "sidebar");
493
- };
532
+ const activate = () => activateCard(occurrence.id);
494
533
  return /* @__PURE__ */ jsx3(
495
534
  "div",
496
535
  {
@@ -520,17 +559,7 @@ function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSide
520
559
  onClick: (event) => {
521
560
  if ((!isActive || collapseOnContentClick) && !renderCard && !event.defaultPrevented && !event.target.closest('button,a,input,select,textarea,summary,[role="button"],[role="link"],[contenteditable="true"],[data-limr-card-interactive]') && window.getSelection()?.isCollapsed !== false) activate();
522
561
  },
523
- children: renderCard ? renderCard({ occurrence, active: isActive, activate }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
524
- references.get(occurrence.id) && /* @__PURE__ */ jsx3("div", { className: "limr-sidebar-reference", title: references.get(occurrence.id), children: /* @__PURE__ */ jsx3("p", { className: "limr-sidebar-excerpt", children: references.get(occurrence.id) }) }),
525
- /* @__PURE__ */ jsxs3("div", { className: "limr-sidebar-card-body", children: [
526
- /* @__PURE__ */ jsxs3("button", { type: "button", className: "limr-sidebar-card-heading", onClick: activate, "aria-expanded": isActive, children: [
527
- definition?.label ?? annotation?.type ?? "Reference",
528
- /* @__PURE__ */ jsx3("span", { className: "limr-sidebar-toggle", children: /* @__PURE__ */ jsx3("span", { className: "limr-button-label limr-underline", children: isActive ? locale.collapse : locale.expand }, String(isActive)) })
529
- ] }),
530
- Preview && annotation && /* @__PURE__ */ jsx3(Collapse, { open: !isActive, keepMounted: true, className: "limr-sidebar-preview-panel", children: /* @__PURE__ */ jsx3("div", { className: "limr-sidebar-preview", children: /* @__PURE__ */ jsx3(Preview, { annotation, data: annotation.data, occurrence, annotations: annotationGroups.get(occurrence.id), referenceText: references.get(occurrence.id) ?? "" }) }) }),
531
- /* @__PURE__ */ jsx3(Collapse, { open: isActive, className: "limr-sidebar-expansion", children: /* @__PURE__ */ jsx3("div", { className: "limr-sidebar-card-content", children: /* @__PURE__ */ jsx3(AnnotationGroup, { occurrence, presentation: "sidebar", enabled: isActive }) }) })
532
- ] })
533
- ] })
562
+ children: renderCard ? renderCard({ occurrence, active: isActive, activate }) : /* @__PURE__ */ jsx3(SidebarCardBody, { occurrence, annotation, annotations: annotationGroups.get(occurrence.id), reference: references.get(occurrence.id) ?? "", active: isActive, title: definition?.label ?? annotation?.type ?? "Reference", toggleLabel: isActive ? locale.collapse : locale.expand, Preview, activate: activateCard })
534
563
  },
535
564
  occurrence.id
536
565
  );
@@ -849,9 +878,33 @@ function AnnotationOverlay({ disabled = false, portalRoot, previewWidth = 340, m
849
878
  }
850
879
 
851
880
  // src/components/SelectionToolbar.tsx
852
- import { useEffect as useEffect6, useId, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
881
+ import { useEffect as useEffect6, useId, useMemo as useMemo2, useRef as useRef5, useState as useState5 } from "react";
853
882
  import { autoUpdate as autoUpdate2, FloatingPortal as FloatingPortal2, offset as offset2, shift as shift2, useFloating as useFloating2 } from "@floating-ui/react";
854
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
883
+
884
+ // src/components/ToolFaces.tsx
885
+ import { useRef as useRef4, useState as useState4 } from "react";
886
+ import { jsx as jsx5 } from "react/jsx-runtime";
887
+ function ToolFaces({ state, children }) {
888
+ const ref = useRef4(null);
889
+ const [width, setWidth] = useState4();
890
+ useIsomorphicLayoutEffect(() => {
891
+ const element = ref.current;
892
+ if (!element) return;
893
+ const faces = [...element.querySelectorAll(".limr-tool-face")];
894
+ const measure = () => {
895
+ const face = faces.find((face2) => face2.hasAttribute("data-visible"));
896
+ if (face) setWidth(face.offsetWidth);
897
+ };
898
+ measure();
899
+ const observer = new ResizeObserver(measure);
900
+ faces.forEach((face) => observer.observe(face));
901
+ return () => observer.disconnect();
902
+ }, [state]);
903
+ return /* @__PURE__ */ jsx5("span", { className: "limr-tool-faces", ref, style: { width }, children });
904
+ }
905
+
906
+ // src/components/SelectionToolbar.tsx
907
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
855
908
  var defaults = [highlightPalette(), highlightSelection(), copyText(), deleteAnnotations()];
856
909
  function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className = "", style, render }) {
857
910
  const runtime = useRuntime();
@@ -870,15 +923,15 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
870
923
  const order = (action) => action.placement === "start" ? -1 : action.placement === "end" ? 1 : 0;
871
924
  return [...map.values()].sort((a, b) => order(a) - order(b));
872
925
  }, [config.extensions, actions]);
873
- const [busy, setBusy] = useState4(null);
874
- const busyRef = useRef4(false);
875
- const [error, setError] = useState4(null);
876
- const [expanded, setExpanded] = useState4(null);
877
- const [success, setSuccess] = useState4(null);
878
- const [deleteHintOpen, setDeleteHintOpen] = useState4(false);
879
- const [preferredHighlight, setPreferredHighlight] = useState4(defaultHighlight);
880
- const closeTimer = useRef4(void 0);
881
- const successTimer = useRef4(void 0);
926
+ const [busy, setBusy] = useState5(null);
927
+ const busyRef = useRef5(false);
928
+ const [error, setError] = useState5(null);
929
+ const [expanded, setExpanded] = useState5(null);
930
+ const [success, setSuccess] = useState5(null);
931
+ const [deleteHintOpen, setDeleteHintOpen] = useState5(false);
932
+ const [preferredHighlight, setPreferredHighlight] = useState5(defaultHighlight);
933
+ const closeTimer = useRef5(void 0);
934
+ const successTimer = useRef5(void 0);
882
935
  const panelId = useId();
883
936
  const documentSnapshot = runtime.document.getSnapshot();
884
937
  const effectiveSelection = useMemo2(() => {
@@ -886,9 +939,15 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
886
939
  if (interaction.editing && active?.annotationIds.includes(interaction.editing)) return null;
887
940
  const range = paint?.range ?? (hiddenActive ? active?.range : void 0);
888
941
  return range ? selectionFromRange(documentSnapshot, range, {
889
- text: paint ? runtime.highlightReference?.text ?? annotationReferenceText(documentSnapshot, { id: paint.id, annotationIds: [], range }) : annotationReferenceText(documentSnapshot, active)
942
+ text: atomicSelectionText(runtime, [range]) ?? (paint ? runtime.highlightReference?.text ?? annotationReferenceText(documentSnapshot, { id: paint.id, annotationIds: [], range }) : annotationReferenceText(documentSnapshot, active))
890
943
  }) : null;
891
944
  }, [documentSnapshot, paint, active, selection, interaction.editing, hiddenActive]);
945
+ const blockReference = useMemo2(() => {
946
+ const ranges = effectiveSelection?.raw.ranges;
947
+ if (ranges?.length !== 1) return null;
948
+ const { start, end } = ranges[0];
949
+ return runtime.content?.querySelector(`[data-limr-atomic="block"][data-source-start="${start}"][data-source-end="${end}"]`) ?? null;
950
+ }, [runtime, effectiveSelection]);
892
951
  const annotations = useMemo2(() => effectiveSelection ? [...decorations.annotations.values()].filter((annotation) => resolveAnnotationAnchors(annotation, documentSnapshot).some((anchor) => [...effectiveSelection.raw.ranges, ...effectiveSelection.syntax.ranges].some((range) => sameRange(anchor.range, range)))) : [], [decorations, effectiveSelection, documentSnapshot]);
893
952
  const highlight = paint ? decorations.annotations.get(paint.annotationId) : annotations.find((annotation) => annotation.type === "highlight");
894
953
  const highlightAppearance = highlight?.data ?? preferredHighlight;
@@ -903,7 +962,8 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
903
962
  signal: runtime.signal,
904
963
  clearSelection: () => runtime.clearSelection()
905
964
  } : null;
906
- const available = context ? combined.filter((action) => action.visible?.(context) ?? true) : [];
965
+ const readOnly = highlight?.data?.readOnly === true;
966
+ const available = context ? readOnly ? [defaults[2]] : combined.filter((action) => action.visible?.(context) ?? true) : [];
907
967
  const expandedAction = available.find((action) => action.id === expanded && action.renderExpanded);
908
968
  const hasPanels = available.some((action) => action.renderExpanded);
909
969
  const open = !!context && effectiveSelection.settled && available.length > 0;
@@ -911,16 +971,16 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
911
971
  open,
912
972
  strategy: "fixed",
913
973
  placement: "bottom",
914
- middleware: [offset2(10), { name: "toolbarSpace", options: { hasPanels, maxPanelHeight }, fn({ rects, placement: placement2, elements }) {
974
+ middleware: [offset2(10), { name: "toolbarSpace", options: { hasPanels, maxPanelHeight, blockReference }, fn({ rects, placement: placement2, elements }) {
915
975
  const viewport = window.visualViewport;
916
976
  const root = runtime.configuration.get().scrollRoot?.getBoundingClientRect();
917
977
  const top = Math.max(viewport?.offsetTop ?? 0, root?.top ?? 0);
918
978
  const bottom = Math.min((viewport?.offsetTop ?? 0) + (viewport?.height ?? window.innerHeight), root?.bottom ?? Infinity);
919
979
  const above = Math.max(0, rects.reference.y - top - 20), below = Math.max(0, bottom - rects.reference.y - rects.reference.height - 20);
920
- const row = (elements.floating.querySelector(".limr-toolbar-actions")?.getBoundingClientRect().height ?? 34) + (elements.floating.querySelector(".limr-toolbar-delete-hint")?.getBoundingClientRect().height ?? 0);
980
+ const row = (elements.floating.querySelector(".limr-toolbar-actions")?.getBoundingClientRect().height ?? 34) + (elements.floating.querySelector(".limr-toolbar-delete-hint,.limr-toolbar-readonly-hint")?.getBoundingClientRect().height ?? 0);
921
981
  const limit = Math.max(0, parseFloat(getComputedStyle(elements.floating).getPropertyValue("--limr-toolbar-panel-max-height")) || maxPanelHeight);
922
982
  const needed = row + (hasPanels ? limit : 0) + 24;
923
- const next = below >= needed || below >= above ? "bottom" : "top";
983
+ const next = blockReference || below >= needed || below >= above ? "bottom" : "top";
924
984
  const room = next === "bottom" ? below : above;
925
985
  elements.floating.style.setProperty("--limr-toolbar-panel-height", `${Math.max(0, Math.min(limit, room - row - 24))}px`);
926
986
  elements.floating.style.setProperty("--limr-toolbar-available-height", `${room}px`);
@@ -929,9 +989,9 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
929
989
  whileElementsMounted: autoUpdate2
930
990
  });
931
991
  useEffect6(() => {
932
- const reference = selection ? runtime.selectionRange : paint ? runtime.highlightReference : active ? runtime.anchors.get(active.id) : null;
933
- if (reference) refs.setPositionReference({ getBoundingClientRect: () => reference.getBoundingClientRect(), getClientRects: () => reference.getClientRects(), contextElement: runtime.content ?? void 0 });
934
- }, [runtime, refs, selection, paint, active]);
992
+ const reference = blockReference ?? (selection ? runtime.selectionRange : paint ? runtime.highlightReference : active ? runtime.anchors.get(active.id) : null);
993
+ if (reference) refs.setPositionReference({ getBoundingClientRect: () => reference.getBoundingClientRect(), getClientRects: () => reference.getClientRects(), contextElement: blockReference ?? runtime.content ?? void 0 });
994
+ }, [runtime, refs, blockReference, selection, paint, active]);
935
995
  const selectionKey = effectiveSelection?.raw.ranges.map((range) => `${range.start}-${range.end}`).join(",") ?? "";
936
996
  useEffect6(() => {
937
997
  setError(null);
@@ -1014,7 +1074,7 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
1014
1074
  };
1015
1075
  const annotationCount = annotations.filter((annotation) => annotation.type !== "highlight").length;
1016
1076
  const hint = available.some((action) => action.id === "delete-annotations") && annotationCount > 1 ? context.locale.deleteAnnotationsHint.replace("{count}", String(annotationCount)) : null;
1017
- return /* @__PURE__ */ jsx5(FloatingPortal2, { root: portalRoot, children: /* @__PURE__ */ jsx5(
1077
+ return /* @__PURE__ */ jsx6(FloatingPortal2, { root: portalRoot, children: /* @__PURE__ */ jsx6(
1018
1078
  "div",
1019
1079
  {
1020
1080
  ref: refs.setFloating,
@@ -1027,6 +1087,7 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
1027
1087
  "data-limr-appearance": appearance,
1028
1088
  "data-placement": placement,
1029
1089
  "data-highlight": highlight?.id,
1090
+ "data-readonly": readOnly || void 0,
1030
1091
  "data-expanded": expandedAction?.id,
1031
1092
  onMouseDown: (event) => {
1032
1093
  if (!(event.target instanceof Element) || !event.target.closest("input,textarea,select,[contenteditable]")) event.preventDefault();
@@ -1058,7 +1119,7 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
1058
1119
  event.preventDefault();
1059
1120
  },
1060
1121
  children: /* @__PURE__ */ jsxs5("div", { className: "limr-toolbar-surface", "data-ready": isPositioned || void 0, children: [
1061
- /* @__PURE__ */ jsx5("div", { className: "limr-toolbar-actions", children: render ? render({ context, actions: available, run }) : available.map((action, index) => /* @__PURE__ */ jsx5(
1122
+ /* @__PURE__ */ jsx6("div", { className: "limr-toolbar-actions", children: render && !readOnly ? render({ context, actions: available, run }) : available.map((action, index) => /* @__PURE__ */ jsx6(
1062
1123
  "div",
1063
1124
  {
1064
1125
  className: "limr-toolbar-item",
@@ -1079,7 +1140,7 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
1079
1140
  setError(cause instanceof Error ? cause.message : String(cause));
1080
1141
  }
1081
1142
  },
1082
- children: action.render ? action.render(actionContext(action)) : /* @__PURE__ */ jsxs5(
1143
+ children: action.render ? action.render(actionContext(action)) : /* @__PURE__ */ jsx6(
1083
1144
  "button",
1084
1145
  {
1085
1146
  className: "limr-toolbar-action",
@@ -1103,33 +1164,34 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
1103
1164
  onBlur: (event) => {
1104
1165
  if (action.id === "delete-annotations" && !event.currentTarget.matches(":hover")) setDeleteHintOpen(false);
1105
1166
  },
1106
- children: [
1167
+ children: /* @__PURE__ */ jsxs5(ToolFaces, { state: label(action), children: [
1107
1168
  /* @__PURE__ */ jsxs5("span", { className: "limr-tool-face", "data-visible": success !== action.id || void 0, "aria-hidden": "true", children: [
1108
- /* @__PURE__ */ jsx5("span", { className: "limr-tool-icon", children: typeof action.icon === "function" ? action.icon(context) : action.icon }, typeof action.label === "function" ? action.label(context) : action.label),
1109
- /* @__PURE__ */ jsx5("span", { className: "limr-tool-label limr-underline", children: typeof action.label === "function" ? action.label(context) : action.label })
1169
+ /* @__PURE__ */ jsx6("span", { className: "limr-tool-icon", children: typeof action.icon === "function" ? action.icon(context) : action.icon }, typeof action.label === "function" ? action.label(context) : action.label),
1170
+ /* @__PURE__ */ jsx6("span", { className: "limr-tool-label limr-underline", children: typeof action.label === "function" ? action.label(context) : action.label })
1110
1171
  ] }),
1111
1172
  action.successLabel && /* @__PURE__ */ jsxs5("span", { className: "limr-tool-face", "data-visible": success === action.id || void 0, "aria-hidden": "true", children: [
1112
- /* @__PURE__ */ jsx5("span", { className: "limr-tool-icon", children: action.successIcon ?? (typeof action.icon === "function" ? action.icon(context) : action.icon) }),
1113
- /* @__PURE__ */ jsx5("span", { className: "limr-tool-label limr-underline", children: typeof action.successLabel === "function" ? action.successLabel(context) : action.successLabel })
1173
+ /* @__PURE__ */ jsx6("span", { className: "limr-tool-icon", children: action.successIcon ?? (typeof action.icon === "function" ? action.icon(context) : action.icon) }),
1174
+ /* @__PURE__ */ jsx6("span", { className: "limr-tool-label limr-underline", children: typeof action.successLabel === "function" ? action.successLabel(context) : action.successLabel })
1114
1175
  ] })
1115
- ]
1176
+ ] })
1116
1177
  }
1117
1178
  )
1118
1179
  },
1119
1180
  action.id
1120
1181
  )) }),
1121
- expandedAction && /* @__PURE__ */ jsx5("div", { id: panelId, className: "limr-toolbar-panel", role: "group", "aria-label": label(expandedAction), children: /* @__PURE__ */ jsx5(OverlayScrollArea, { active: true, label: label(expandedAction), children: expandedAction.renderExpanded(actionContext(expandedAction)) }) }, expandedAction.id),
1122
- hint && /* @__PURE__ */ jsx5(Collapse, { open: deleteHintOpen, keepMounted: true, className: "limr-toolbar-delete-warning", children: /* @__PURE__ */ jsx5("p", { id: `${panelId}-delete`, className: "limr-toolbar-delete-hint", children: hint }) }),
1123
- error && /* @__PURE__ */ jsx5("span", { role: "alert", className: "limr-action-error", children: error })
1182
+ readOnly && /* @__PURE__ */ jsx6("p", { className: "limr-toolbar-readonly-hint", children: context.locale.inlineHighlightHint ?? "Markdown inline highlight, editing unavailable" }),
1183
+ expandedAction && /* @__PURE__ */ jsx6("div", { id: panelId, className: "limr-toolbar-panel", role: "group", "aria-label": label(expandedAction), children: /* @__PURE__ */ jsx6(OverlayScrollArea, { active: true, label: label(expandedAction), children: expandedAction.renderExpanded(actionContext(expandedAction)) }) }, expandedAction.id),
1184
+ hint && /* @__PURE__ */ jsx6(Collapse, { open: deleteHintOpen, keepMounted: true, className: "limr-toolbar-delete-warning", children: /* @__PURE__ */ jsx6("p", { id: `${panelId}-delete`, className: "limr-toolbar-delete-hint", children: hint }) }),
1185
+ error && /* @__PURE__ */ jsx6("span", { role: "alert", className: "limr-action-error", children: error })
1124
1186
  ] }, selectionKey)
1125
1187
  }
1126
1188
  ) });
1127
1189
  }
1128
1190
 
1129
1191
  // src/components/Reader.tsx
1130
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1192
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1131
1193
  function Reader({ actions, toc = true, sidebar = true, followOutput = false, sidebarBreakpoint = 1120, fillHeight = true, className, style, ...provider }) {
1132
- return /* @__PURE__ */ jsx6(DocumentProvider, { ...provider, children: /* @__PURE__ */ jsx6(ReaderLayout, { actions, toc, sidebar, followOutput, sidebarBreakpoint, fillHeight, className, style }) });
1194
+ return /* @__PURE__ */ jsx7(DocumentProvider, { ...provider, children: /* @__PURE__ */ jsx7(ReaderLayout, { actions, toc, sidebar, followOutput, sidebarBreakpoint, fillHeight, className, style }) });
1133
1195
  }
1134
1196
  function ReaderLayout({ actions, toc, sidebar, followOutput, sidebarBreakpoint, fillHeight, className, style }) {
1135
1197
  const runtime = useRuntime();
@@ -1137,12 +1199,12 @@ function ReaderLayout({ actions, toc, sidebar, followOutput, sidebarBreakpoint,
1137
1199
  const layout = useStoreSelector(runtime.layout, (value) => value);
1138
1200
  const appearance = useAppearance();
1139
1201
  const theme = useTheme();
1140
- const ref = useRef5(null);
1141
- const tocRail = useRef5(null);
1142
- const content = useRef5(null);
1202
+ const ref = useRef6(null);
1203
+ const tocRail = useRef6(null);
1204
+ const content = useRef6(null);
1143
1205
  useOverflowFade(content, runtime, ":scope > .limr-prose");
1144
- const [width, setWidth] = useState5(0);
1145
- const [availableHeight, setAvailableHeight] = useState5(0);
1206
+ const [width, setWidth] = useState6(0);
1207
+ const [availableHeight, setAvailableHeight] = useState6(0);
1146
1208
  useIsomorphicLayoutEffect(() => {
1147
1209
  if (!fillHeight || !ref.current) return;
1148
1210
  const origin = ref.current.getBoundingClientRect().top - runtime.getViewportTop() - (scrollRoot?.clientTop ?? 0) + runtime.getScrollTop();
@@ -1203,25 +1265,25 @@ function ReaderLayout({ actions, toc, sidebar, followOutput, sidebarBreakpoint,
1203
1265
  const sidebarVisible = !!sidebar && width >= sidebarBreakpoint;
1204
1266
  return /* @__PURE__ */ jsxs6("div", { ref, className: `limr-reader ${className ?? ""}`, style: { "--limr-reader-available-height": fillHeight ? `${availableHeight}px` : void 0, ...style }, "data-fill-height": fillHeight || void 0, "data-sidebar": sidebarVisible || void 0, "data-toc": toc && width >= 800 || void 0, "data-limr-theme": theme, "data-limr-appearance": appearance, children: [
1205
1267
  toc && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1206
- /* @__PURE__ */ jsx6("div", { ref: tocRail, className: "limr-reader-toc", children: /* @__PURE__ */ jsx6(TableOfContents, {}) }),
1207
- /* @__PURE__ */ jsx6(MobileContents, {})
1268
+ /* @__PURE__ */ jsx7("div", { ref: tocRail, className: "limr-reader-toc", children: /* @__PURE__ */ jsx7(TableOfContents, {}) }),
1269
+ /* @__PURE__ */ jsx7(MobileContents, {})
1208
1270
  ] }),
1209
- /* @__PURE__ */ jsx6("main", { ref: content, className: "limr-reader-content", children: /* @__PURE__ */ jsx6(Markdown, { followOutput }) }),
1210
- sidebarVisible && /* @__PURE__ */ jsx6(AnnotationSidebar, {}),
1211
- /* @__PURE__ */ jsx6(AnnotationOverlay, { disabled: sidebarVisible }),
1212
- /* @__PURE__ */ jsx6(SelectionToolbar, { actions })
1271
+ /* @__PURE__ */ jsx7("main", { ref: content, className: "limr-reader-content", children: /* @__PURE__ */ jsx7(Markdown, { followOutput }) }),
1272
+ sidebarVisible && /* @__PURE__ */ jsx7(AnnotationSidebar, {}),
1273
+ /* @__PURE__ */ jsx7(AnnotationOverlay, { disabled: sidebarVisible }),
1274
+ /* @__PURE__ */ jsx7(SelectionToolbar, { actions })
1213
1275
  ] });
1214
1276
  }
1215
1277
  function MobileContents() {
1216
1278
  const locale = useLocale();
1217
- const [open, setOpen] = useState5(false);
1279
+ const [open, setOpen] = useState6(false);
1218
1280
  const id = useId2();
1219
1281
  return /* @__PURE__ */ jsxs6("div", { className: "limr-reader-mobile-toc", children: [
1220
1282
  /* @__PURE__ */ jsxs6("button", { type: "button", className: "limr-mobile-toc-toggle", "aria-expanded": open, "aria-controls": id, onClick: () => setOpen((value) => !value), children: [
1221
- /* @__PURE__ */ jsx6("span", { children: locale.contents }),
1222
- /* @__PURE__ */ jsx6("svg", { viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx6("path", { d: "m6 8 4 4 4-4" }) })
1283
+ /* @__PURE__ */ jsx7("span", { children: locale.contents }),
1284
+ /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "m6 8 4 4 4-4" }) })
1223
1285
  ] }),
1224
- /* @__PURE__ */ jsx6("div", { id, children: /* @__PURE__ */ jsx6(Collapse, { open, keepMounted: true, children: /* @__PURE__ */ jsx6(TableOfContents, {}) }) })
1286
+ /* @__PURE__ */ jsx7("div", { id, children: /* @__PURE__ */ jsx7(Collapse, { open, keepMounted: true, children: /* @__PURE__ */ jsx7(TableOfContents, {}) }) })
1225
1287
  ] });
1226
1288
  }
1227
1289
 
@@ -1235,4 +1297,4 @@ export {
1235
1297
  SelectionToolbar,
1236
1298
  Reader
1237
1299
  };
1238
- //# sourceMappingURL=chunk-N77VD2YR.js.map
1300
+ //# sourceMappingURL=chunk-GU2RPESF.js.map