@linloir/limr 0.1.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 (97) hide show
  1. package/README.en.md +151 -0
  2. package/README.md +80 -127
  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 +93 -10
  10. package/dist/{chunk-7JF5HHVF.js → chunk-BG5QUQFR.js} +340 -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-ETHLPBKQ.js → chunk-GU2RPESF.js} +238 -131
  18. package/dist/chunk-GU2RPESF.js.map +1 -0
  19. package/dist/{chunk-65V5OZ4G.js → chunk-SUAAUGAX.js} +1350 -511
  20. package/dist/chunk-SUAAUGAX.js.map +1 -0
  21. package/dist/core/index.d.ts +6 -3
  22. package/dist/core/index.js +15 -5
  23. package/dist/extensions/index.d.ts +30 -5
  24. package/dist/extensions/index.js +15 -3
  25. package/dist/index.d.ts +4 -4
  26. package/dist/index.js +31 -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 +7 -5
  31. package/dist/react/index.js +7 -3
  32. package/dist/styles.css +93 -10
  33. package/dist/{translations-BliabrPd.d.ts → translations-BXzSwLBJ.d.ts} +49 -4
  34. package/docs/README.md +13 -0
  35. package/docs/api.md +5 -158
  36. package/docs/assets/rendered.svg +9 -0
  37. package/docs/assets/source.svg +9 -0
  38. package/docs/en/README.md +28 -0
  39. package/docs/en/ai-skills.md +57 -0
  40. package/docs/en/annotations.md +86 -0
  41. package/docs/en/api.md +115 -0
  42. package/docs/en/architecture.md +41 -0
  43. package/docs/en/extensions.md +208 -0
  44. package/docs/en/getting-started.md +69 -0
  45. package/docs/en/integration.md +154 -0
  46. package/docs/en/modules.md +84 -0
  47. package/docs/en/performance.md +93 -0
  48. package/docs/en/plugin-packages.md +68 -0
  49. package/docs/en/styles.md +99 -0
  50. package/docs/en/syntax.md +455 -0
  51. package/docs/extensions.md +5 -306
  52. package/docs/implementation.md +5 -76
  53. package/docs/performance.md +5 -81
  54. package/docs/screenshots/blocks.png +0 -0
  55. package/docs/screenshots/dark.png +0 -0
  56. package/docs/screenshots/desktop.png +0 -0
  57. package/docs/screenshots/plugin-mobile.png +0 -0
  58. package/docs/styles.md +5 -100
  59. package/docs/visual-upgrade.md +5 -145
  60. package/docs/zh-CN/README.md +28 -0
  61. package/docs/zh-CN/ai-skills.md +57 -0
  62. package/docs/zh-CN/annotations.md +86 -0
  63. package/docs/zh-CN/api.md +115 -0
  64. package/docs/zh-CN/architecture.md +41 -0
  65. package/docs/zh-CN/extensions.md +210 -0
  66. package/docs/zh-CN/getting-started.md +69 -0
  67. package/docs/zh-CN/integration.md +154 -0
  68. package/docs/zh-CN/modules.md +84 -0
  69. package/docs/zh-CN/performance.md +93 -0
  70. package/docs/zh-CN/plugin-packages.md +68 -0
  71. package/docs/zh-CN/styles.md +99 -0
  72. package/docs/zh-CN/syntax.md +457 -0
  73. package/examples/notes.tsx +18 -11
  74. package/examples/packages/paper-theme/index.css +25 -0
  75. package/examples/packages/paper-theme/package.json +7 -0
  76. package/examples/packages/reading-tools/index.d.ts +2 -0
  77. package/examples/packages/reading-tools/index.js +25 -0
  78. package/examples/packages/reading-tools/package.json +16 -0
  79. package/package.json +17 -5
  80. package/skills/limr-plugin-development/SKILL.md +22 -0
  81. package/skills/limr-plugin-development/agents/openai.yaml +4 -0
  82. package/skills/limr-plugin-development/assets/plugin-template/README.md +30 -0
  83. package/skills/limr-plugin-development/assets/plugin-template/package.json +36 -0
  84. package/skills/limr-plugin-development/assets/plugin-template/src/index.tsx +50 -0
  85. package/skills/limr-plugin-development/assets/plugin-template/src/styles.css +9 -0
  86. package/skills/limr-plugin-development/assets/plugin-template/tsconfig.json +20 -0
  87. package/skills/limr-plugin-development/assets/plugin-template/tsup.config.ts +8 -0
  88. package/skills/limr-plugin-development/references/contract.md +51 -0
  89. package/skills/limr-use/SKILL.md +23 -0
  90. package/skills/limr-use/agents/openai.yaml +4 -0
  91. package/skills/limr-use/references/integration.md +49 -0
  92. package/skills/limr-use/references/syntax.md +20 -0
  93. package/dist/chunk-65V5OZ4G.js.map +0 -1
  94. package/dist/chunk-7JF5HHVF.js.map +0 -1
  95. package/dist/chunk-ETHLPBKQ.js.map +0 -1
  96. package/dist/chunk-V2OG4Y3F.js +0 -1
  97. /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-65V5OZ4G.js";
21
+ } from "./chunk-SUAAUGAX.js";
23
22
  import {
23
+ annotationReferenceRange,
24
24
  defaultHighlight,
25
- intersects,
26
25
  mapText,
27
- rangeOf,
28
26
  resolveAnnotationAnchors,
29
27
  sameRange,
30
- selectionFromRange,
31
- textContent
32
- } from "./chunk-7JF5HHVF.js";
28
+ selectionFromRange
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
@@ -236,39 +260,29 @@ function AnnotationGroup({ occurrence, presentation, expanded = true, enabled =
236
260
 
237
261
  // src/components/annotation-reference.ts
238
262
  function annotationReferenceText(document2, occurrence) {
239
- const blocks = document2.blocks.filter((block) => intersects(block.range, occurrence.range));
263
+ const referenceRange = annotationReferenceRange(document2, occurrence);
264
+ const blocks = document2.blocks.filter((block) => intersects(block.range, referenceRange));
240
265
  let reference = "";
241
- if (occurrence.embedded) {
242
- let context;
243
- const visit = (node) => {
244
- if (!intersects(rangeOf(node), occurrence.range)) return;
245
- if (["paragraph", "heading", "listItem"].includes(node.type)) context = node;
246
- node.children?.forEach(visit);
247
- };
248
- blocks.forEach((block) => visit(block.node));
249
- reference = context ? textContent(context) : "";
250
- } else {
251
- const visit = (node) => {
252
- const range = rangeOf(node);
253
- if (!intersects(range, occurrence.range) || ["annotationReference", "footnoteReference", "annotationDefinition", "definition"].includes(node.type)) return;
254
- if (node.value !== void 0) {
255
- const mapping = mapText(document2.source, node.value, range);
256
- reference += node.value.split("").filter((_, index) => mapping.ends[index] > occurrence.range.start && mapping.starts[index] < occurrence.range.end).join("");
257
- } else if (node.type === "image") reference += String(node.alt ?? "");
258
- else node.children?.forEach(visit);
259
- };
260
- blocks.forEach((block, index) => {
261
- if (index) reference += " ";
262
- visit(block.node);
263
- });
264
- reference ||= occurrence.quote ?? document2.source.slice(occurrence.range.start, occurrence.range.end);
265
- }
266
+ const visit = (node) => {
267
+ const range = rangeOf(node);
268
+ if (!intersects(range, referenceRange) || ["annotationReference", "footnoteReference", "annotationDefinition", "definition"].includes(node.type)) return;
269
+ if (node.value !== void 0) {
270
+ const mapping = mapText(document2.source, node.value, range);
271
+ reference += node.value.split("").filter((_, index) => mapping.ends[index] > referenceRange.start && mapping.starts[index] < referenceRange.end).join("");
272
+ } else if (node.type === "image") reference += String(node.alt ?? "");
273
+ else node.children?.forEach(visit);
274
+ };
275
+ blocks.forEach((block, index) => {
276
+ if (index) reference += " ";
277
+ visit(block.node);
278
+ });
279
+ if (!occurrence.embedded) reference ||= occurrence.quote ?? document2.source.slice(referenceRange.start, referenceRange.end);
266
280
  return reference.replace(/\s+/g, " ").trim();
267
281
  }
268
282
 
269
283
  // src/components/AnnotationSidebar.tsx
270
284
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
271
- var alignSidebar = (items, { activeId, gap, height }) => {
285
+ var alignSidebar = (items, { activeId, gap, height, prioritizeActive }) => {
272
286
  const positions = items.map((item) => item.anchorTop);
273
287
  const activeIndex = items.findIndex((item) => item.id === activeId);
274
288
  const pivot = activeIndex < 0 ? 0 : activeIndex;
@@ -277,11 +291,25 @@ var alignSidebar = (items, { activeId, gap, height }) => {
277
291
  for (let i = pivot - 1; i >= 0; i--) positions[i] = Math.min(items[i].anchorTop, positions[i + 1] - items[i].height - gap);
278
292
  for (let i = pivot + 1; i < items.length; i++) positions[i] = Math.max(items[i].anchorTop, positions[i - 1] + items[i - 1].height + gap);
279
293
  if (height !== void 0 && items.reduce((sum, item) => sum + item.height, gap * (items.length - 1)) <= height) {
280
- for (let i = items.length - 1; i >= 0; i--) positions[i] = Math.min(positions[i], (i === items.length - 1 ? height : positions[i + 1] - gap) - items[i].height);
281
- for (let i = 0; i < items.length; i++) positions[i] = Math.max(positions[i], i === 0 ? 0 : positions[i - 1] + items[i - 1].height + gap);
294
+ const start = prioritizeActive && activeIndex >= 0 ? pivot + 1 : 0;
295
+ for (let i = items.length - 1; i >= start; i--) positions[i] = Math.min(positions[i], (i === items.length - 1 ? height : positions[i + 1] - gap) - items[i].height);
296
+ if (start || activeIndex < 0 || positions[0] >= 0) for (let i = start; i < items.length; i++) positions[i] = Math.max(positions[i], i === 0 ? 0 : positions[i - 1] + items[i - 1].height + gap);
282
297
  }
283
298
  return new Map(items.map((item, index) => [item.id, positions[index]]));
284
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
+ });
285
313
  function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSidebar, renderCard, className, style }) {
286
314
  const runtime = useRuntime();
287
315
  const document2 = useDocument();
@@ -298,12 +326,16 @@ function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSide
298
326
  const rail = useRef2(null);
299
327
  const cards = useRef2(/* @__PURE__ */ new Map());
300
328
  const [sizes, setSizes] = useState2(/* @__PURE__ */ new Map());
329
+ const [minimumSizes, setMinimumSizes] = useState2(/* @__PURE__ */ new Map());
301
330
  const [anchorTops, setAnchorTops] = useState2(/* @__PURE__ */ new Map());
302
331
  const [railHeight, setRailHeight] = useState2(0);
303
- const measured = useRef2({ sizes: /* @__PURE__ */ new Map(), height: 0 });
332
+ const measured = useRef2({ sizes: /* @__PURE__ */ new Map(), minimum: /* @__PURE__ */ new Map(), height: 0 });
304
333
  const resizeFrame = useRef2(0);
305
334
  const [viewportTop, setViewportTop] = useState2(0);
335
+ const [viewportHeight, setViewportHeight] = useState2(0);
306
336
  const [cardLock, setCardLock] = useState2(null);
337
+ const lastFocus = useRef2(null);
338
+ const [release, setRelease] = useState2(null);
307
339
  const occurrences = useMemo(() => {
308
340
  const values = [...decorations.occurrences].sort((a, b) => a.range.start - b.range.start || a.range.end - b.range.end || a.id.localeCompare(b.id));
309
341
  if (sync === "none") {
@@ -318,13 +350,20 @@ function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSide
318
350
  })])), [decorations, occurrences]);
319
351
  const measure = useCallback((beforePaint = false) => {
320
352
  const next = /* @__PURE__ */ new Map();
321
- cards.current.forEach((element2, id) => next.set(id, element2.getBoundingClientRect().height));
353
+ const minimum = /* @__PURE__ */ new Map();
354
+ cards.current.forEach((element2, id) => {
355
+ const height2 = element2.getBoundingClientRect().height;
356
+ next.set(id, height2);
357
+ const panels = [...element2.querySelectorAll(":scope > .limr-sidebar-card-body > .limr-collapse")];
358
+ minimum.set(id, height2 - panels.reduce((sum, panel) => sum + panel.getBoundingClientRect().height, 0));
359
+ });
322
360
  const height = rail.current?.getBoundingClientRect().height ?? 0;
323
361
  const old = measured.current;
324
- if (old.height === height && old.sizes.size === next.size && [...next].every(([id, size2]) => size2 === old.sizes.get(id))) return;
325
- measured.current = { sizes: next, height };
362
+ if (old.height === height && old.sizes.size === next.size && [...next].every(([id, size2]) => size2 === old.sizes.get(id) && Math.abs(minimum.get(id) - (old.minimum.get(id) ?? 0)) < 0.5)) return;
363
+ measured.current = { sizes: next, minimum, height };
326
364
  const update = () => {
327
365
  setSizes(next);
366
+ setMinimumSizes(minimum);
328
367
  setRailHeight(height);
329
368
  };
330
369
  if (!beforePaint) {
@@ -357,13 +396,19 @@ function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSide
357
396
  for (const occurrence of occurrences) {
358
397
  const marker = runtime.anchors.get(occurrence.id);
359
398
  if (!marker || marker.closest("[inert]")) continue;
360
- const rect = !occurrence.embedded && sourceRangeStartRect(runtime, occurrence.range);
399
+ const rect = sourceRangeStartRect(runtime, decorations.referenceRanges.get(occurrence.id) ?? occurrence.range);
361
400
  values.set(occurrence.id, (rect || marker.getBoundingClientRect()).top - top - displacement2);
362
401
  }
363
402
  setAnchorTops((old) => old.size === values.size && [...values].every(([id, value]) => Math.abs(value - (old.get(id) ?? -Infinity)) < 0.5) ? old : values);
364
- }, [runtime, occurrences, layout, sync]);
403
+ }, [runtime, decorations.referenceRanges, occurrences, layout, sync]);
365
404
  useIsomorphicLayoutEffect(() => {
366
- if (sync === "document" && rail.current) setViewportTop(Math.max(0, runtime.getViewportTop() - rail.current.getBoundingClientRect().top + (runtime.configuration.get().scrollOffset ?? 24)));
405
+ if (sync !== "document" || !rail.current) return;
406
+ const root = runtime.configuration.get().scrollRoot;
407
+ const bounds = rail.current.getBoundingClientRect();
408
+ const top = Math.max(bounds.top, runtime.getViewportTop() + (root?.clientTop ?? 0), window.visualViewport?.offsetTop ?? 0);
409
+ const bottom = Math.min(bounds.bottom, root ? runtime.getViewportTop() + root.clientTop + root.clientHeight : Infinity, (window.visualViewport?.offsetTop ?? 0) + (window.visualViewport?.height ?? innerHeight));
410
+ setViewportTop(Math.max(0, top - bounds.top + (runtime.configuration.get().scrollOffset ?? 24)));
411
+ setViewportHeight(Math.max(0, bottom - top));
367
412
  }, [runtime, layout, viewport, sync]);
368
413
  useEffect4(() => {
369
414
  const dismiss = (event) => {
@@ -395,35 +440,79 @@ function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSide
395
440
  if (sync !== "none") items.sort((a, b) => (anchorTops.get(a.id) ?? 0) - (anchorTops.get(b.id) ?? 0));
396
441
  const readingId = items.find((item) => (anchorTops.get(item.id) ?? 0) >= viewportTop)?.id ?? items.at(-1)?.id ?? null;
397
442
  const alignmentId = hovered ?? active ?? readingId;
443
+ const focusedId = hovered ?? active;
398
444
  const source = hovered ? hoverSource : active ? activeSource : null;
399
445
  const locked = source === "sidebar" && cardLock?.id === alignmentId ? cardLock : null;
446
+ const lockedTop = locked?.top;
447
+ useIsomorphicLayoutEffect(() => {
448
+ const previous = lastFocus.current;
449
+ lastFocus.current = focusedId ? { id: focusedId, top: lockedTop } : null;
450
+ if (focusedId || sync !== "document" || !rail.current || matchMedia("(prefers-reduced-motion: reduce)").matches) {
451
+ setRelease(null);
452
+ return;
453
+ }
454
+ if (!previous) return;
455
+ const style2 = getComputedStyle(rail.current), value = style2.getPropertyValue("--limr-motion-slow").trim();
456
+ const milliseconds = parseFloat(value) * (value.endsWith("ms") ? 1 : 1e3);
457
+ const duration = Number.isFinite(milliseconds) ? Math.max(0, milliseconds) : 460;
458
+ if (!duration) {
459
+ setRelease(null);
460
+ return;
461
+ }
462
+ const motion = rail.current.animate([{}, {}], { duration, easing: style2.getPropertyValue("--limr-ease").trim() || "ease-out" });
463
+ setRelease({ ...previous, progress: 0 });
464
+ let frame = 0;
465
+ const step = () => {
466
+ if (motion.playState === "finished") {
467
+ setRelease(null);
468
+ return;
469
+ }
470
+ setRelease({ ...previous, progress: motion.effect?.getComputedTiming().progress ?? 0 });
471
+ frame = requestAnimationFrame(step);
472
+ };
473
+ frame = requestAnimationFrame(step);
474
+ return () => {
475
+ cancelAnimationFrame(frame);
476
+ motion.cancel();
477
+ };
478
+ }, [focusedId, lockedTop, sync]);
400
479
  const sidebarItems = items.map((item) => ({ id: item.id, anchorTop: anchorTops.get(item.id) ?? 0, height: sizes.get(item.id) ?? 80 }));
401
- const positionContext = { activeId: alignmentId, gap, height: sync === "document" && railHeight > 0 ? railHeight : void 0 };
402
- const positions = positioner(locked ? sidebarItems.map((item) => item.id === locked.id ? { ...item, anchorTop: locked.top } : item) : sidebarItems, positionContext);
480
+ const positionContext = { activeId: alignmentId, gap, height: sync === "document" && railHeight > 0 ? railHeight : void 0, prioritizeActive: !!(hovered || active) };
481
+ const positions = new Map(positioner(locked ? sidebarItems.map((item) => item.id === locked.id ? { ...item, anchorTop: locked.top } : item) : sidebarItems, positionContext));
482
+ const releasing = !focusedId && release;
483
+ if (releasing) {
484
+ const origin = positioner(sidebarItems.map((item) => item.id === releasing.id && releasing.top !== void 0 ? { ...item, anchorTop: releasing.top } : item), { ...positionContext, activeId: releasing.id, prioritizeActive: true });
485
+ for (const [id, top] of positions) positions.set(id, top + ((origin.get(id) ?? top) - top) * (1 - releasing.progress));
486
+ }
403
487
  const lockedAnchor = locked ? anchorTops.get(locked.id) ?? locked.top : 0;
404
- const anchorFits = lockedAnchor >= 0 && lockedAnchor + (sizes.get(locked?.id ?? "") ?? 80) <= railHeight;
405
- const lockFits = locked && (positions.get(locked.id) ?? locked.top) >= locked.top - 0.5;
406
- const displacement = sync === "document" && locked && anchorFits && lockFits ? (positions.get(locked.id) ?? locked.top) - lockedAnchor : 0;
488
+ const displacement = sync === "document" && locked ? (positions.get(locked.id) ?? locked.top) - lockedAnchor : 0;
407
489
  useIsomorphicLayoutEffect(() => {
408
490
  if (sync === "document") runtime.documentOffset.set(displacement);
409
491
  }, [runtime, displacement, sync]);
410
- const lockCard = (id) => {
492
+ const lockCard = useCallback((id) => {
411
493
  if (locked?.id === id) return;
412
494
  const card = cards.current.get(id);
413
495
  if (card && rail.current) {
414
496
  const origin = rail.current.getBoundingClientRect().top;
415
497
  const top = card.getBoundingClientRect().top - origin;
416
- const anchorTop = anchorTops.get(id) ?? top;
417
- const boundedTop = positioner(sidebarItems, { ...positionContext, activeId: id }).get(id) ?? anchorTop;
418
- setCardLock({ id, top: boundedTop < anchorTop - 0.5 ? anchorTop : top });
498
+ setCardLock({ id, top });
419
499
  }
420
- };
421
- return /* @__PURE__ */ jsx3("aside", { ref: rail, "aria-label": locale.annotations, className: `limr-sidebar ${className ?? ""}`, "data-sync": sync, "data-limr-theme": theme, "data-limr-appearance": appearance, style, children: items.map((occurrence) => {
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]);
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) => {
422
509
  const annotation = decorations.annotations.get(occurrence.annotationIds[0]);
423
510
  const definition = runtime.annotationTypes.get(annotation?.type ?? "citation");
424
511
  const isActive = active === occurrence.id;
425
512
  const palette = definition?.sidebar;
426
513
  const Preview = palette?.Preview;
514
+ const available = source === "marker" && alignmentId === occurrence.id && !isActive ? Math.max(minimumSizes.get(occurrence.id) ?? 80, railHeight - (anchorTops.get(occurrence.id) ?? 0)) : railHeight;
515
+ const cardHeight = isActive ? Math.min(available, Math.max(minimumSizes.get(occurrence.id) ?? 80, viewportHeight || railHeight)) : available;
427
516
  const cardStyle = {
428
517
  "--limr-annotation-color": palette?.color ?? definition?.color,
429
518
  "--limr-annotation-border": palette?.borderColor,
@@ -436,16 +525,11 @@ function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSide
436
525
  position: "absolute",
437
526
  top: positions.get(occurrence.id) ?? 0,
438
527
  insetInline: 0,
439
- "--limr-sidebar-card-height": `${railHeight}px`
528
+ "--limr-sidebar-card-height": `${cardHeight}px`
440
529
  } : { marginBlockEnd: gap }
441
530
  };
442
531
  const collapseOnContentClick = occurrence.annotationIds.every((id) => runtime.annotationTypes.get(decorations.annotations.get(id)?.type ?? "")?.sidebar?.collapseOnContentClick);
443
- const activate = () => {
444
- const hovering = runtime.interaction.get().hovered === occurrence.id || cards.current.get(occurrence.id)?.matches(":hover");
445
- lockCard(occurrence.id);
446
- runtime.activate(isActive ? null : occurrence.id, "sidebar");
447
- if (hovering) runtime.hover(occurrence.id, "sidebar");
448
- };
532
+ const activate = () => activateCard(occurrence.id);
449
533
  return /* @__PURE__ */ jsx3(
450
534
  "div",
451
535
  {
@@ -475,17 +559,7 @@ function AnnotationSidebar({ sync = "document", gap = 14, positioner = alignSide
475
559
  onClick: (event) => {
476
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();
477
561
  },
478
- children: renderCard ? renderCard({ occurrence, active: isActive, activate }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
479
- 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) }) }),
480
- /* @__PURE__ */ jsxs3("div", { className: "limr-sidebar-card-body", children: [
481
- /* @__PURE__ */ jsxs3("button", { type: "button", className: "limr-sidebar-card-heading", onClick: activate, "aria-expanded": isActive, children: [
482
- definition?.label ?? annotation?.type ?? "Reference",
483
- /* @__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)) })
484
- ] }),
485
- 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) ?? "" }) }) }),
486
- /* @__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 }) }) })
487
- ] })
488
- ] })
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 })
489
563
  },
490
564
  occurrence.id
491
565
  );
@@ -804,9 +878,33 @@ function AnnotationOverlay({ disabled = false, portalRoot, previewWidth = 340, m
804
878
  }
805
879
 
806
880
  // src/components/SelectionToolbar.tsx
807
- 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";
808
882
  import { autoUpdate as autoUpdate2, FloatingPortal as FloatingPortal2, offset as offset2, shift as shift2, useFloating as useFloating2 } from "@floating-ui/react";
809
- 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";
810
908
  var defaults = [highlightPalette(), highlightSelection(), copyText(), deleteAnnotations()];
811
909
  function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className = "", style, render }) {
812
910
  const runtime = useRuntime();
@@ -825,15 +923,15 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
825
923
  const order = (action) => action.placement === "start" ? -1 : action.placement === "end" ? 1 : 0;
826
924
  return [...map.values()].sort((a, b) => order(a) - order(b));
827
925
  }, [config.extensions, actions]);
828
- const [busy, setBusy] = useState4(null);
829
- const busyRef = useRef4(false);
830
- const [error, setError] = useState4(null);
831
- const [expanded, setExpanded] = useState4(null);
832
- const [success, setSuccess] = useState4(null);
833
- const [deleteHintOpen, setDeleteHintOpen] = useState4(false);
834
- const [preferredHighlight, setPreferredHighlight] = useState4(defaultHighlight);
835
- const closeTimer = useRef4(void 0);
836
- 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);
837
935
  const panelId = useId();
838
936
  const documentSnapshot = runtime.document.getSnapshot();
839
937
  const effectiveSelection = useMemo2(() => {
@@ -841,9 +939,15 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
841
939
  if (interaction.editing && active?.annotationIds.includes(interaction.editing)) return null;
842
940
  const range = paint?.range ?? (hiddenActive ? active?.range : void 0);
843
941
  return range ? selectionFromRange(documentSnapshot, range, {
844
- 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))
845
943
  }) : null;
846
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]);
847
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]);
848
952
  const highlight = paint ? decorations.annotations.get(paint.annotationId) : annotations.find((annotation) => annotation.type === "highlight");
849
953
  const highlightAppearance = highlight?.data ?? preferredHighlight;
@@ -858,7 +962,8 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
858
962
  signal: runtime.signal,
859
963
  clearSelection: () => runtime.clearSelection()
860
964
  } : null;
861
- 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) : [];
862
967
  const expandedAction = available.find((action) => action.id === expanded && action.renderExpanded);
863
968
  const hasPanels = available.some((action) => action.renderExpanded);
864
969
  const open = !!context && effectiveSelection.settled && available.length > 0;
@@ -866,16 +971,16 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
866
971
  open,
867
972
  strategy: "fixed",
868
973
  placement: "bottom",
869
- 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 }) {
870
975
  const viewport = window.visualViewport;
871
976
  const root = runtime.configuration.get().scrollRoot?.getBoundingClientRect();
872
977
  const top = Math.max(viewport?.offsetTop ?? 0, root?.top ?? 0);
873
978
  const bottom = Math.min((viewport?.offsetTop ?? 0) + (viewport?.height ?? window.innerHeight), root?.bottom ?? Infinity);
874
979
  const above = Math.max(0, rects.reference.y - top - 20), below = Math.max(0, bottom - rects.reference.y - rects.reference.height - 20);
875
- 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);
876
981
  const limit = Math.max(0, parseFloat(getComputedStyle(elements.floating).getPropertyValue("--limr-toolbar-panel-max-height")) || maxPanelHeight);
877
982
  const needed = row + (hasPanels ? limit : 0) + 24;
878
- const next = below >= needed || below >= above ? "bottom" : "top";
983
+ const next = blockReference || below >= needed || below >= above ? "bottom" : "top";
879
984
  const room = next === "bottom" ? below : above;
880
985
  elements.floating.style.setProperty("--limr-toolbar-panel-height", `${Math.max(0, Math.min(limit, room - row - 24))}px`);
881
986
  elements.floating.style.setProperty("--limr-toolbar-available-height", `${room}px`);
@@ -884,9 +989,9 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
884
989
  whileElementsMounted: autoUpdate2
885
990
  });
886
991
  useEffect6(() => {
887
- const reference = selection ? runtime.selectionRange : paint ? runtime.highlightReference : active ? runtime.anchors.get(active.id) : null;
888
- if (reference) refs.setPositionReference({ getBoundingClientRect: () => reference.getBoundingClientRect(), getClientRects: () => reference.getClientRects(), contextElement: runtime.content ?? void 0 });
889
- }, [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]);
890
995
  const selectionKey = effectiveSelection?.raw.ranges.map((range) => `${range.start}-${range.end}`).join(",") ?? "";
891
996
  useEffect6(() => {
892
997
  setError(null);
@@ -969,7 +1074,7 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
969
1074
  };
970
1075
  const annotationCount = annotations.filter((annotation) => annotation.type !== "highlight").length;
971
1076
  const hint = available.some((action) => action.id === "delete-annotations") && annotationCount > 1 ? context.locale.deleteAnnotationsHint.replace("{count}", String(annotationCount)) : null;
972
- return /* @__PURE__ */ jsx5(FloatingPortal2, { root: portalRoot, children: /* @__PURE__ */ jsx5(
1077
+ return /* @__PURE__ */ jsx6(FloatingPortal2, { root: portalRoot, children: /* @__PURE__ */ jsx6(
973
1078
  "div",
974
1079
  {
975
1080
  ref: refs.setFloating,
@@ -982,6 +1087,7 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
982
1087
  "data-limr-appearance": appearance,
983
1088
  "data-placement": placement,
984
1089
  "data-highlight": highlight?.id,
1090
+ "data-readonly": readOnly || void 0,
985
1091
  "data-expanded": expandedAction?.id,
986
1092
  onMouseDown: (event) => {
987
1093
  if (!(event.target instanceof Element) || !event.target.closest("input,textarea,select,[contenteditable]")) event.preventDefault();
@@ -1013,7 +1119,7 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
1013
1119
  event.preventDefault();
1014
1120
  },
1015
1121
  children: /* @__PURE__ */ jsxs5("div", { className: "limr-toolbar-surface", "data-ready": isPositioned || void 0, children: [
1016
- /* @__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(
1017
1123
  "div",
1018
1124
  {
1019
1125
  className: "limr-toolbar-item",
@@ -1034,7 +1140,7 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
1034
1140
  setError(cause instanceof Error ? cause.message : String(cause));
1035
1141
  }
1036
1142
  },
1037
- children: action.render ? action.render(actionContext(action)) : /* @__PURE__ */ jsxs5(
1143
+ children: action.render ? action.render(actionContext(action)) : /* @__PURE__ */ jsx6(
1038
1144
  "button",
1039
1145
  {
1040
1146
  className: "limr-toolbar-action",
@@ -1058,33 +1164,34 @@ function SelectionToolbar({ actions, portalRoot, maxPanelHeight = 260, className
1058
1164
  onBlur: (event) => {
1059
1165
  if (action.id === "delete-annotations" && !event.currentTarget.matches(":hover")) setDeleteHintOpen(false);
1060
1166
  },
1061
- children: [
1167
+ children: /* @__PURE__ */ jsxs5(ToolFaces, { state: label(action), children: [
1062
1168
  /* @__PURE__ */ jsxs5("span", { className: "limr-tool-face", "data-visible": success !== action.id || void 0, "aria-hidden": "true", children: [
1063
- /* @__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),
1064
- /* @__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 })
1065
1171
  ] }),
1066
1172
  action.successLabel && /* @__PURE__ */ jsxs5("span", { className: "limr-tool-face", "data-visible": success === action.id || void 0, "aria-hidden": "true", children: [
1067
- /* @__PURE__ */ jsx5("span", { className: "limr-tool-icon", children: action.successIcon ?? (typeof action.icon === "function" ? action.icon(context) : action.icon) }),
1068
- /* @__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 })
1069
1175
  ] })
1070
- ]
1176
+ ] })
1071
1177
  }
1072
1178
  )
1073
1179
  },
1074
1180
  action.id
1075
1181
  )) }),
1076
- 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),
1077
- 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 }) }),
1078
- 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 })
1079
1186
  ] }, selectionKey)
1080
1187
  }
1081
1188
  ) });
1082
1189
  }
1083
1190
 
1084
1191
  // src/components/Reader.tsx
1085
- 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";
1086
1193
  function Reader({ actions, toc = true, sidebar = true, followOutput = false, sidebarBreakpoint = 1120, fillHeight = true, className, style, ...provider }) {
1087
- 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 }) });
1088
1195
  }
1089
1196
  function ReaderLayout({ actions, toc, sidebar, followOutput, sidebarBreakpoint, fillHeight, className, style }) {
1090
1197
  const runtime = useRuntime();
@@ -1092,12 +1199,12 @@ function ReaderLayout({ actions, toc, sidebar, followOutput, sidebarBreakpoint,
1092
1199
  const layout = useStoreSelector(runtime.layout, (value) => value);
1093
1200
  const appearance = useAppearance();
1094
1201
  const theme = useTheme();
1095
- const ref = useRef5(null);
1096
- const tocRail = useRef5(null);
1097
- const content = useRef5(null);
1202
+ const ref = useRef6(null);
1203
+ const tocRail = useRef6(null);
1204
+ const content = useRef6(null);
1098
1205
  useOverflowFade(content, runtime, ":scope > .limr-prose");
1099
- const [width, setWidth] = useState5(0);
1100
- const [availableHeight, setAvailableHeight] = useState5(0);
1206
+ const [width, setWidth] = useState6(0);
1207
+ const [availableHeight, setAvailableHeight] = useState6(0);
1101
1208
  useIsomorphicLayoutEffect(() => {
1102
1209
  if (!fillHeight || !ref.current) return;
1103
1210
  const origin = ref.current.getBoundingClientRect().top - runtime.getViewportTop() - (scrollRoot?.clientTop ?? 0) + runtime.getScrollTop();
@@ -1158,25 +1265,25 @@ function ReaderLayout({ actions, toc, sidebar, followOutput, sidebarBreakpoint,
1158
1265
  const sidebarVisible = !!sidebar && width >= sidebarBreakpoint;
1159
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: [
1160
1267
  toc && /* @__PURE__ */ jsxs6(Fragment3, { children: [
1161
- /* @__PURE__ */ jsx6("div", { ref: tocRail, className: "limr-reader-toc", children: /* @__PURE__ */ jsx6(TableOfContents, {}) }),
1162
- /* @__PURE__ */ jsx6(MobileContents, {})
1268
+ /* @__PURE__ */ jsx7("div", { ref: tocRail, className: "limr-reader-toc", children: /* @__PURE__ */ jsx7(TableOfContents, {}) }),
1269
+ /* @__PURE__ */ jsx7(MobileContents, {})
1163
1270
  ] }),
1164
- /* @__PURE__ */ jsx6("main", { ref: content, className: "limr-reader-content", children: /* @__PURE__ */ jsx6(Markdown, { followOutput }) }),
1165
- sidebarVisible && /* @__PURE__ */ jsx6(AnnotationSidebar, {}),
1166
- /* @__PURE__ */ jsx6(AnnotationOverlay, { disabled: sidebarVisible }),
1167
- /* @__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 })
1168
1275
  ] });
1169
1276
  }
1170
1277
  function MobileContents() {
1171
1278
  const locale = useLocale();
1172
- const [open, setOpen] = useState5(false);
1279
+ const [open, setOpen] = useState6(false);
1173
1280
  const id = useId2();
1174
1281
  return /* @__PURE__ */ jsxs6("div", { className: "limr-reader-mobile-toc", children: [
1175
1282
  /* @__PURE__ */ jsxs6("button", { type: "button", className: "limr-mobile-toc-toggle", "aria-expanded": open, "aria-controls": id, onClick: () => setOpen((value) => !value), children: [
1176
- /* @__PURE__ */ jsx6("span", { children: locale.contents }),
1177
- /* @__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" }) })
1178
1285
  ] }),
1179
- /* @__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, {}) }) })
1180
1287
  ] });
1181
1288
  }
1182
1289
 
@@ -1190,4 +1297,4 @@ export {
1190
1297
  SelectionToolbar,
1191
1298
  Reader
1192
1299
  };
1193
- //# sourceMappingURL=chunk-ETHLPBKQ.js.map
1300
+ //# sourceMappingURL=chunk-GU2RPESF.js.map