@plannotator/ui 0.39.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HANDOFF.md +140 -10
- package/README.md +9 -5
- package/components/AnnotationPanel.tsx +244 -13
- package/components/CommentPopover.tsx +91 -2
- package/components/DiagramBlock.tsx +376 -0
- package/components/GraphvizBlock.tsx +22 -597
- package/components/HtmlSurfaceControls.tsx +188 -23
- package/components/ListMarker.tsx +10 -1
- package/components/MermaidBlock.tsx +17 -630
- package/components/TableOfContents.tsx +5 -1
- package/components/TerminalToolsAnnouncementDialog.tsx +454 -0
- package/components/Viewer.tsx +67 -3
- package/components/blocks/AlertBlock.tsx +7 -2
- package/components/diagram/DiagramCanvas.tsx +431 -0
- package/components/diagram/DiagramComposer.tsx +135 -0
- package/components/diagram/DiagramOverlay.tsx +215 -0
- package/components/diagram/DiagramPopout.tsx +70 -0
- package/components/diagram/DiagramSourcePane.tsx +244 -0
- package/components/diagram/DiagramViewer.tsx +276 -0
- package/components/diagram/anchorClaims.ts +71 -0
- package/components/diagram/index.ts +36 -0
- package/components/diagram/useDiagramComments.ts +341 -0
- package/components/diagram/useDiagramRender.ts +91 -0
- package/components/diagram/useDiagramSourceDraft.ts +143 -0
- package/components/diagram/useDiagramViewport.ts +156 -0
- package/components/html-viewer/HtmlViewer.tsx +32 -0
- package/components/html-viewer/bridge-script.asset.js +121 -9
- package/components/html-viewer/bridge-script.lite.ts +1 -1
- package/components/html-viewer/bridge-script.ts +133 -9
- package/components/html-viewer/useHtmlAnnotation.ts +44 -151
- package/hooks/useAnnotationHighlighter.ts +469 -14
- package/hooks/useLinkedDoc.ts +100 -9
- package/package.json +6 -4
- package/shortcuts/plan-review/htmlAnnotate.shortcuts.ts +21 -7
- package/styles.css +1 -1
- package/theme.css +62 -0
- package/types.ts +5 -0
- package/utils/annotationScope.ts +159 -0
- package/utils/cssColor.ts +463 -0
- package/utils/diagram-anchor-graphviz.ts +143 -0
- package/utils/diagram-anchor.ts +401 -0
- package/utils/diagram-projection.ts +66 -0
- package/utils/diagram-render.ts +668 -0
- package/utils/graphviz.ts +93 -0
- package/utils/htmlChrome.ts +70 -5
- package/utils/htmlLinkNavigation.ts +196 -0
- package/utils/mermaid-eager.ts +13 -11
- package/utils/mermaid.ts +19 -10
- package/utils/mermaidTheme.ts +732 -0
- package/utils/parser.ts +36 -7
- package/utils/terminalToolsAnnouncement.ts +76 -0
- package/components/mermaidSvg.ts +0 -33
|
@@ -1,38 +1,135 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Header controls for a raw-HTML or live-app annotation surface: the
|
|
3
|
-
* (show/hide the floating tools over
|
|
4
|
-
* the pen (Annotate/Interact toggle).
|
|
5
|
-
* in the host. Each control renders only
|
|
6
|
-
* read-only document can show the eye
|
|
2
|
+
* Header controls for a raw-HTML or live-app annotation surface: the optional
|
|
3
|
+
* back (leave a linked document), the eye (show/hide the floating tools over
|
|
4
|
+
* the page), the optional refresh, and the pen (Annotate/Interact toggle).
|
|
5
|
+
* Presentation only; every state lives in the host. Each control renders only
|
|
6
|
+
* when its handler is passed, so a read-only document can show the eye
|
|
7
|
+
* without a pen.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* Back exists because the sidebar's "Viewing / Back to …" header is not a
|
|
10
|
+
* dependable way out of a raw-HTML linked document: an HTML surface opens
|
|
11
|
+
* with the sidebar closed and navigating between HTML documents deliberately
|
|
12
|
+
* leaves it closed, so the way back has to live in the header. It carries no
|
|
13
|
+
* keyboard shortcut — `Alt`+`Left` and the browser's own Back are the user's,
|
|
14
|
+
* not ours.
|
|
15
|
+
*
|
|
16
|
+
* The markup, data attributes (`data-html-back`, `data-html-tools-toggle`,
|
|
17
|
+
* `data-html-refresh`, `data-html-annotate-toggle`), aria state and the pixel-stable pen border
|
|
10
18
|
* are the exact ones Plannotator's header shipped with; hosts get the same
|
|
11
19
|
* control, and `labels` overrides the strings without touching the DOM.
|
|
20
|
+
*
|
|
21
|
+
* Each control's description rides the app's `Tooltip` (hover AND
|
|
22
|
+
* focus-visible, theme tokens, portal) instead of a native `title`, with the
|
|
23
|
+
* control's keyboard shortcut rendered under it as keycaps through the
|
|
24
|
+
* shortcut registry's platform-aware formatter — never a hardcoded "Cmd".
|
|
25
|
+
* The strings are the same `labels` values the titles used, so a host's
|
|
26
|
+
* overrides keep working; because `title` no longer supplies the accessible
|
|
27
|
+
* name, each button carries it explicitly (the pen an `aria-label`, the eye
|
|
28
|
+
* its sr-only text, the refresh its existing `aria-label`), and the shortcut
|
|
29
|
+
* is also attached as a persistent `aria-describedby` so it is announced
|
|
30
|
+
* rather than only drawn.
|
|
12
31
|
*/
|
|
32
|
+
import React from 'react';
|
|
33
|
+
import { Tooltip } from './Tooltip';
|
|
34
|
+
import { formatShortcutBindingText, formatShortcutBindingTokens } from '../shortcuts/core';
|
|
35
|
+
import { htmlAnnotateShortcuts } from '../shortcuts/plan-review/htmlAnnotate.shortcuts';
|
|
36
|
+
|
|
37
|
+
/** Binding overrides, in the registry's normalized syntax (`Mod+Shift+X`).
|
|
38
|
+
* `null` renders no shortcut row for that control. Defaults come from the
|
|
39
|
+
* `html-annotate` scope, so the tooltips can never drift from the chords the
|
|
40
|
+
* app actually dispatches; `refresh` has no chord and defaults to none. */
|
|
41
|
+
export interface HtmlSurfaceControlShortcuts {
|
|
42
|
+
annotate?: string | null;
|
|
43
|
+
tools?: string | null;
|
|
44
|
+
refresh?: string | null;
|
|
45
|
+
back?: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const DEFAULT_HTML_SURFACE_CONTROL_SHORTCUTS: Required<HtmlSurfaceControlShortcuts> = {
|
|
49
|
+
annotate: htmlAnnotateShortcuts.shortcuts.toggleAnnotateMode.bindings[0] ?? null,
|
|
50
|
+
tools: htmlAnnotateShortcuts.shortcuts.toggleTools.bindings[0] ?? null,
|
|
51
|
+
refresh: null,
|
|
52
|
+
// Deliberately none: Alt+Left and the browser's Back belong to the user.
|
|
53
|
+
back: null,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Description + keycaps. Two lines, so the tooltip answers both "what does
|
|
57
|
+
* this do" and "what do I press" without a second hover. */
|
|
58
|
+
function ControlTooltip({
|
|
59
|
+
description,
|
|
60
|
+
binding,
|
|
61
|
+
children,
|
|
62
|
+
}: {
|
|
63
|
+
description: string;
|
|
64
|
+
binding: string | null;
|
|
65
|
+
children: React.ReactElement;
|
|
66
|
+
}) {
|
|
67
|
+
const keys = binding ? formatShortcutBindingTokens(binding) : null;
|
|
68
|
+
return (
|
|
69
|
+
<Tooltip
|
|
70
|
+
side="bottom"
|
|
71
|
+
wide
|
|
72
|
+
content={(
|
|
73
|
+
<span className="flex flex-col gap-1">
|
|
74
|
+
<span>{description}</span>
|
|
75
|
+
{keys && keys.length > 0 && (
|
|
76
|
+
<span className="flex items-center gap-1">
|
|
77
|
+
{keys.map((key, index) => (
|
|
78
|
+
<kbd
|
|
79
|
+
key={`${key}-${index}`}
|
|
80
|
+
className="inline-flex h-[18px] min-w-[18px] items-center justify-center rounded border border-border/60 bg-muted px-1 font-mono text-[10px] leading-none text-foreground/80"
|
|
81
|
+
>
|
|
82
|
+
{key}
|
|
83
|
+
</kbd>
|
|
84
|
+
))}
|
|
85
|
+
</span>
|
|
86
|
+
)}
|
|
87
|
+
</span>
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{children}
|
|
91
|
+
</Tooltip>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The shortcut as prose for assistive tech ("Cmd+Shift+X"), which keycaps
|
|
96
|
+
* alone would not announce. */
|
|
97
|
+
function ShortcutDescription({ id, binding }: { id: string; binding: string | null }) {
|
|
98
|
+
if (!binding) return null;
|
|
99
|
+
return (
|
|
100
|
+
<span id={id} hidden>
|
|
101
|
+
{`Shortcut: ${formatShortcutBindingText(binding)}`}
|
|
102
|
+
</span>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
13
105
|
|
|
14
106
|
/** String overrides. Every key optional; defaults are Plannotator's strings. */
|
|
15
107
|
export interface HtmlSurfaceControlLabels {
|
|
16
|
-
/** Pen
|
|
108
|
+
/** Pen tooltip description while Annotate is armed. */
|
|
17
109
|
annotateTitle?: string;
|
|
18
|
-
/** Pen
|
|
110
|
+
/** Pen tooltip description while in Interact mode. */
|
|
19
111
|
interactTitle?: string;
|
|
20
|
-
/** Pen aria-label while armed. Default:
|
|
112
|
+
/** Pen aria-label while armed. Default: the armed description. */
|
|
21
113
|
annotateLabel?: string;
|
|
22
|
-
/** Pen aria-label while in Interact mode. Default:
|
|
114
|
+
/** Pen aria-label while in Interact mode. Default: the interact description. */
|
|
23
115
|
interactLabel?: string;
|
|
24
|
-
/** Eye
|
|
116
|
+
/** Eye tooltip description and screen-reader text while the tools are visible. */
|
|
25
117
|
hideTools?: string;
|
|
26
|
-
/** Eye
|
|
118
|
+
/** Eye tooltip description and screen-reader text while the tools are hidden. */
|
|
27
119
|
showTools?: string;
|
|
28
120
|
/** Refresh visible text while idle. */
|
|
29
121
|
refresh?: string;
|
|
30
122
|
/** Refresh visible text while a refresh is in flight. */
|
|
31
123
|
refreshing?: string;
|
|
32
|
-
/** Refresh
|
|
124
|
+
/** Refresh tooltip description and aria-label while idle. */
|
|
33
125
|
refreshTitle?: string;
|
|
34
|
-
/** Refresh
|
|
126
|
+
/** Refresh tooltip description and aria-label while a refresh is in flight. */
|
|
35
127
|
refreshingTitle?: string;
|
|
128
|
+
/** Back visible text. */
|
|
129
|
+
back?: string;
|
|
130
|
+
/** Back tooltip description and aria-label. `backDescription` overrides it
|
|
131
|
+
* per render, which is how a host names the document Back returns to. */
|
|
132
|
+
backTitle?: string;
|
|
36
133
|
}
|
|
37
134
|
|
|
38
135
|
export const DEFAULT_HTML_SURFACE_CONTROL_LABELS: Required<
|
|
@@ -46,8 +143,16 @@ export const DEFAULT_HTML_SURFACE_CONTROL_LABELS: Required<
|
|
|
46
143
|
refreshing: 'Refreshing',
|
|
47
144
|
refreshTitle: 'Refresh document',
|
|
48
145
|
refreshingTitle: 'Refreshing document',
|
|
146
|
+
back: 'Back',
|
|
147
|
+
backTitle: 'Back to the document this one was opened from',
|
|
49
148
|
};
|
|
50
149
|
|
|
150
|
+
// Stable ids: one HTML surface renders at most one of each control.
|
|
151
|
+
const ANNOTATE_SHORTCUT_ID = 'pn-html-annotate-shortcut';
|
|
152
|
+
const TOOLS_SHORTCUT_ID = 'pn-html-tools-shortcut';
|
|
153
|
+
const REFRESH_SHORTCUT_ID = 'pn-html-refresh-shortcut';
|
|
154
|
+
const BACK_SHORTCUT_ID = 'pn-html-back-shortcut';
|
|
155
|
+
|
|
51
156
|
export interface HtmlSurfaceControlsProps {
|
|
52
157
|
/** Whether Annotate is armed (pen pressed). */
|
|
53
158
|
armed: boolean;
|
|
@@ -62,9 +167,17 @@ export interface HtmlSurfaceControlsProps {
|
|
|
62
167
|
canRefresh?: boolean;
|
|
63
168
|
onRefresh?: () => void;
|
|
64
169
|
isRefreshing?: boolean;
|
|
170
|
+
/** Leave the linked document for the one the session opened from. The back
|
|
171
|
+
* control renders only when provided — i.e. while a linked document is open. */
|
|
172
|
+
onBack?: () => void;
|
|
173
|
+
/** Tooltip description and aria-label for back, e.g. "Back to index.html".
|
|
174
|
+
* Default: `labels.backTitle`. */
|
|
175
|
+
backDescription?: string;
|
|
65
176
|
/** Compact touch shells put these actions in a menu instead: render nothing. */
|
|
66
177
|
compact?: boolean;
|
|
67
178
|
labels?: HtmlSurfaceControlLabels;
|
|
179
|
+
/** Per-control keyboard shortcuts shown in the tooltips. */
|
|
180
|
+
shortcuts?: HtmlSurfaceControlShortcuts;
|
|
68
181
|
}
|
|
69
182
|
|
|
70
183
|
export function HtmlSurfaceControls({
|
|
@@ -75,16 +188,59 @@ export function HtmlSurfaceControls({
|
|
|
75
188
|
canRefresh = false,
|
|
76
189
|
onRefresh,
|
|
77
190
|
isRefreshing = false,
|
|
191
|
+
onBack,
|
|
192
|
+
backDescription,
|
|
78
193
|
compact = false,
|
|
79
194
|
labels,
|
|
195
|
+
shortcuts,
|
|
80
196
|
}: HtmlSurfaceControlsProps) {
|
|
81
197
|
if (compact) return null;
|
|
82
198
|
const text = { ...DEFAULT_HTML_SURFACE_CONTROL_LABELS, ...labels };
|
|
83
|
-
const
|
|
199
|
+
const keys = { ...DEFAULT_HTML_SURFACE_CONTROL_SHORTCUTS, ...shortcuts };
|
|
200
|
+
const penDescription = armed ? text.annotateTitle : text.interactTitle;
|
|
201
|
+
// `title` used to carry the pen's accessible name; with the tooltip in its
|
|
202
|
+
// place the name has to be stated, or the pen becomes an unnamed button.
|
|
203
|
+
const penLabel = (armed ? labels?.annotateLabel : labels?.interactLabel) ?? penDescription;
|
|
204
|
+
const toolsDescription = toolsHidden ? text.showTools : text.hideTools;
|
|
205
|
+
const refreshDescription = isRefreshing ? text.refreshingTitle : text.refreshTitle;
|
|
84
206
|
const showRefresh = canRefresh && !!onRefresh;
|
|
207
|
+
const backLabel = backDescription ?? text.backTitle;
|
|
85
208
|
return (
|
|
86
209
|
<>
|
|
87
|
-
{/*
|
|
210
|
+
{/* Back, leftmost: the way out of a linked document that does not
|
|
211
|
+
depend on the sidebar being open. */}
|
|
212
|
+
{onBack && (
|
|
213
|
+
<>
|
|
214
|
+
<ControlTooltip description={backLabel} binding={keys.back}>
|
|
215
|
+
<button
|
|
216
|
+
type="button"
|
|
217
|
+
data-html-back
|
|
218
|
+
onClick={onBack}
|
|
219
|
+
{...(keys.back ? { 'aria-describedby': BACK_SHORTCUT_ID } : {})}
|
|
220
|
+
className="ml-1 flex items-center gap-1 rounded px-1.5 py-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
|
|
221
|
+
aria-label={backLabel}
|
|
222
|
+
>
|
|
223
|
+
<svg
|
|
224
|
+
aria-hidden="true"
|
|
225
|
+
viewBox="0 0 24 24"
|
|
226
|
+
fill="none"
|
|
227
|
+
stroke="currentColor"
|
|
228
|
+
strokeWidth="2"
|
|
229
|
+
strokeLinecap="round"
|
|
230
|
+
strokeLinejoin="round"
|
|
231
|
+
className="h-3.5 w-3.5"
|
|
232
|
+
>
|
|
233
|
+
<path d="M19 12H5M12 19l-7-7 7-7" />
|
|
234
|
+
</svg>
|
|
235
|
+
<span className="hidden sm:inline">{text.back}</span>
|
|
236
|
+
</button>
|
|
237
|
+
</ControlTooltip>
|
|
238
|
+
<ShortcutDescription id={BACK_SHORTCUT_ID} binding={keys.back} />
|
|
239
|
+
</>
|
|
240
|
+
)}
|
|
241
|
+
|
|
242
|
+
{/* The refresh and the eye share one group, left of the pen and right
|
|
243
|
+
of back. Each
|
|
88
244
|
renders on its own terms: the refresh whenever it is offered
|
|
89
245
|
(canRefresh + onRefresh), the eye whenever onToggleTools is passed,
|
|
90
246
|
so a host without the tools toggle still gets its refresh.
|
|
@@ -96,6 +252,7 @@ export function HtmlSurfaceControls({
|
|
|
96
252
|
{(showRefresh || onToggleTools) && (
|
|
97
253
|
<div className="ml-1 flex items-center gap-0.5">
|
|
98
254
|
{showRefresh && (
|
|
255
|
+
<ControlTooltip description={refreshDescription} binding={keys.refresh}>
|
|
99
256
|
<button
|
|
100
257
|
type="button"
|
|
101
258
|
data-html-refresh
|
|
@@ -104,9 +261,9 @@ export function HtmlSurfaceControls({
|
|
|
104
261
|
// dedups in-flight requests, so an extra click is harmless.
|
|
105
262
|
onClick={isRefreshing ? undefined : onRefresh}
|
|
106
263
|
aria-disabled={isRefreshing}
|
|
264
|
+
{...(keys.refresh ? { 'aria-describedby': REFRESH_SHORTCUT_ID } : {})}
|
|
107
265
|
className="flex items-center gap-1.5 rounded px-1.5 py-1 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground aria-disabled:cursor-wait aria-disabled:opacity-70"
|
|
108
|
-
|
|
109
|
-
aria-label={isRefreshing ? text.refreshingTitle : text.refreshTitle}
|
|
266
|
+
aria-label={refreshDescription}
|
|
110
267
|
>
|
|
111
268
|
<svg
|
|
112
269
|
aria-hidden="true"
|
|
@@ -123,15 +280,18 @@ export function HtmlSurfaceControls({
|
|
|
123
280
|
</svg>
|
|
124
281
|
<span className="hidden sm:inline">{isRefreshing ? text.refreshing : text.refresh}</span>
|
|
125
282
|
</button>
|
|
283
|
+
</ControlTooltip>
|
|
126
284
|
)}
|
|
285
|
+
{showRefresh && <ShortcutDescription id={REFRESH_SHORTCUT_ID} binding={keys.refresh} />}
|
|
127
286
|
{onToggleTools && (
|
|
287
|
+
<ControlTooltip description={toolsDescription} binding={keys.tools}>
|
|
128
288
|
<button
|
|
129
289
|
type="button"
|
|
130
290
|
data-html-tools-toggle
|
|
131
291
|
onClick={onToggleTools}
|
|
132
292
|
aria-pressed={toolsHidden}
|
|
293
|
+
{...(keys.tools ? { 'aria-describedby': TOOLS_SHORTCUT_ID } : {})}
|
|
133
294
|
className="cursor-pointer rounded-md border border-transparent p-1.5 text-xs font-medium text-muted-foreground transition-all hover:bg-muted hover:text-foreground"
|
|
134
|
-
title={toolsHidden ? text.showTools : text.hideTools}
|
|
135
295
|
>
|
|
136
296
|
{toolsHidden ? (
|
|
137
297
|
<svg aria-hidden="true" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
@@ -143,9 +303,11 @@ export function HtmlSurfaceControls({
|
|
|
143
303
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
|
144
304
|
</svg>
|
|
145
305
|
)}
|
|
146
|
-
<span className="sr-only">{
|
|
306
|
+
<span className="sr-only">{toolsDescription}</span>
|
|
147
307
|
</button>
|
|
308
|
+
</ControlTooltip>
|
|
148
309
|
)}
|
|
310
|
+
{onToggleTools && <ShortcutDescription id={TOOLS_SHORTCUT_ID} binding={keys.tools} />}
|
|
149
311
|
</div>
|
|
150
312
|
)}
|
|
151
313
|
|
|
@@ -156,6 +318,7 @@ export function HtmlSurfaceControls({
|
|
|
156
318
|
unarmed is muted with a TRANSPARENT border of the same width, so
|
|
157
319
|
the button's box is pixel-identical in both states. */}
|
|
158
320
|
{onToggleArmed && (
|
|
321
|
+
<ControlTooltip description={penDescription} binding={keys.annotate}>
|
|
159
322
|
<button
|
|
160
323
|
type="button"
|
|
161
324
|
data-html-annotate-toggle
|
|
@@ -166,14 +329,16 @@ export function HtmlSurfaceControls({
|
|
|
166
329
|
? 'border-primary/60 bg-primary/15 text-primary'
|
|
167
330
|
: 'border-transparent text-muted-foreground hover:text-foreground hover:bg-muted'
|
|
168
331
|
}`}
|
|
169
|
-
|
|
170
|
-
{...(
|
|
332
|
+
aria-label={penLabel}
|
|
333
|
+
{...(keys.annotate ? { 'aria-describedby': ANNOTATE_SHORTCUT_ID } : {})}
|
|
171
334
|
>
|
|
172
335
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
173
336
|
<path strokeLinecap="round" strokeLinejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.862 4.487zm0 0L19.5 7.125" />
|
|
174
337
|
</svg>
|
|
175
338
|
</button>
|
|
339
|
+
</ControlTooltip>
|
|
176
340
|
)}
|
|
341
|
+
{onToggleArmed && <ShortcutDescription id={ANNOTATE_SHORTCUT_ID} binding={keys.annotate} />}
|
|
177
342
|
</>
|
|
178
343
|
);
|
|
179
344
|
}
|
|
@@ -15,6 +15,15 @@ import React from 'react';
|
|
|
15
15
|
*
|
|
16
16
|
* Interactivity is opt-in: the Viewer passes `interactive` + `onToggle`
|
|
17
17
|
* for click-to-toggle checkboxes; the diff view leaves both undefined.
|
|
18
|
+
*
|
|
19
|
+
* The marker carries `annotation-exclude` because it is `select-none`: the
|
|
20
|
+
* browser leaves the bullet/numeral out of every selection string, so it is
|
|
21
|
+
* out of the quote an annotation stores. Without the class the highlighter
|
|
22
|
+
* still PAINTS it (a selection crossing two list items wraps the second item's
|
|
23
|
+
* bullet on the way), and the restore verification then compares a painted
|
|
24
|
+
* "a•b" against a stored "a\nb", rejects a perfectly good restore, and
|
|
25
|
+
* the annotation comes back from a reload with no highlight at all. Excluding
|
|
26
|
+
* it makes the two agree by construction.
|
|
18
27
|
*/
|
|
19
28
|
interface ListMarkerProps {
|
|
20
29
|
level: number;
|
|
@@ -45,7 +54,7 @@ export const ListMarker: React.FC<ListMarkerProps> = ({
|
|
|
45
54
|
|
|
46
55
|
return (
|
|
47
56
|
<span
|
|
48
|
-
className={`select-none shrink-0 self-start flex items-center gap-1${interactive ? ' cursor-pointer' : ''}`}
|
|
57
|
+
className={`annotation-exclude select-none shrink-0 self-start flex items-center gap-1${interactive ? ' cursor-pointer' : ''}`}
|
|
49
58
|
onClick={handleClick}
|
|
50
59
|
role={interactive ? 'checkbox' : undefined}
|
|
51
60
|
aria-checked={interactive ? checked : undefined}
|