@plannotator/ui 0.30.0 → 0.32.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/README.md +46 -1
- package/components/ActionMenu.tsx +6 -1
- package/components/AgentsTab.tsx +8 -9
- package/components/AnalysisLayerToggle.tsx +48 -0
- package/components/AnnotationPanel.tsx +158 -36
- package/components/AnnotationToolbar.tsx +50 -30
- package/components/AnnotationToolstrip.tsx +9 -0
- package/components/CommentPopover.tsx +238 -46
- package/components/ConfirmDialog.tsx +42 -28
- package/components/GraphvizBlock.tsx +86 -7
- package/components/HtmlSurfaceControls.tsx +170 -0
- package/components/InlineMarkdown.tsx +25 -4
- package/components/KeyboardShortcuts.tsx +9 -0
- package/components/Landing.tsx +1 -1
- package/components/LookAndFeelAnnouncementDialog.tsx +147 -178
- package/components/MarkdownEditor/embedPicker.ts +349 -0
- package/components/MarkdownEditor.tsx +12 -0
- package/components/MermaidBlock.tsx +60 -26
- package/components/ModeToggle.tsx +2 -1
- package/components/PermissionModeSetup.tsx +24 -5
- package/components/PinpointOverlay.tsx +11 -6
- package/components/PlanHeaderMenu.tsx +140 -1
- package/components/SearchableSelect.tsx +2 -0
- package/components/Settings.tsx +175 -10
- package/components/SkillReferenceMenu.tsx +9 -0
- package/components/StickyHeaderLane.tsx +9 -2
- package/components/TableOfContents.tsx +9 -4
- package/components/TextShimmer.tsx +8 -5
- package/components/ThemeProvider.tsx +43 -1
- package/components/ThemeTab.tsx +52 -1
- package/components/Tooltip.tsx +3 -1
- package/components/Viewer.tsx +19 -5
- package/components/VimTargetReticle.tsx +12 -4
- package/components/ai/DocumentAIChatPanel.tsx +1 -0
- package/components/blocks/MathBlock.tsx +26 -14
- package/components/core/button.tsx +14 -6
- package/components/html-viewer/HtmlViewer.tsx +320 -41
- package/components/html-viewer/bridge-script.ts +360 -72
- package/components/html-viewer/composerYield.ts +1 -51
- package/components/html-viewer/hostThreads.ts +37 -0
- package/components/html-viewer/index.ts +9 -0
- package/components/html-viewer/unanchored.ts +47 -0
- package/components/html-viewer/useHtmlAnnotation.ts +240 -61
- package/components/plan-diff/PlanCleanDiffView.tsx +1 -0
- package/components/sidebar/FileBrowser.tsx +17 -5
- package/components/sidebar/SidebarContainer.tsx +124 -28
- package/components/ui/button.tsx +10 -8
- package/components/ui/dialog.tsx +35 -25
- package/config/index.ts +6 -1
- package/config/reviewView.ts +42 -9
- package/config/settings.ts +141 -0
- package/configure.ts +32 -0
- package/hooks/useAIProviderConfig.ts +8 -7
- package/hooks/useActiveSection.ts +6 -4
- package/hooks/useAgentJobs.ts +3 -0
- package/hooks/useAnnotationHighlighter.ts +20 -0
- package/hooks/useHtmlRefresh.ts +149 -0
- package/hooks/useIsMobile.ts +37 -0
- package/hooks/useLinkedDoc.ts +7 -0
- package/hooks/useMathRenderer.ts +30 -0
- package/hooks/useScrollViewport.ts +74 -0
- package/hooks/useSharing.ts +31 -5
- package/hooks/useViewportEnvironment.ts +350 -0
- package/package.json +5 -2
- package/shortcuts/index.ts +3 -0
- package/shortcuts/plan-review/annotationMode.shortcuts.ts +91 -0
- package/shortcuts/plan-review/documentView.shortcuts.ts +26 -0
- package/shortcuts/plan-review/htmlAnnotate.shortcuts.ts +24 -0
- package/styles.css +1 -1
- package/theme.css +229 -0
- package/types.ts +35 -0
- package/utils/annotateAgentTerminal.ts +36 -5
- package/utils/blockTargeting.ts +6 -3
- package/utils/composerYield.ts +45 -0
- package/utils/generateIdentity.ts +64 -14
- package/utils/htmlChrome.ts +20 -16
- package/utils/identity-tater.ts +36 -0
- package/utils/lookAndFeelAnnouncement.ts +12 -8
- package/utils/markdownExtensions.ts +57 -0
- package/utils/math-eager.ts +25 -0
- package/utils/math.ts +146 -0
- package/utils/mermaid-eager.ts +28 -0
- package/utils/mermaid.ts +132 -0
- package/utils/parser.ts +75 -2
- package/utils/quickLabels.ts +13 -0
- package/utils/vimNavigation.ts +4 -1
- package/utils/vimScroll.ts +9 -4
- package/utils/wideMode.ts +20 -0
- package/webmcp/activity.ts +46 -0
- package/webmcp/changes.ts +227 -0
- package/webmcp/index.ts +72 -0
- package/webmcp/modelContext.ts +103 -0
- package/webmcp/nudges.ts +174 -0
- package/webmcp/policy.ts +50 -0
- package/webmcp/preference.ts +50 -0
- package/webmcp/schema.ts +81 -0
- package/webmcp/toolset.ts +337 -0
- package/webmcp/useToolset.ts +74 -0
- package/components/PlanAIAnnouncementDialog.tsx +0 -187
- package/components/VimModeAnnouncementDialog.tsx +0 -557
- package/utils/planAIAnnouncement.ts +0 -17
- package/utils/vimModeAnnouncement.ts +0 -23
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { useEffect, useMemo, useSyncExternalStore } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface VisualViewportSnapshot {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
offsetTop: number;
|
|
7
|
+
offsetLeft: number;
|
|
8
|
+
scale: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ViewportEnvironmentInput {
|
|
12
|
+
layoutWidth: number;
|
|
13
|
+
layoutHeight: number;
|
|
14
|
+
visualViewport?: VisualViewportSnapshot | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ViewportEnvironment {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
offsetTop: number;
|
|
21
|
+
offsetLeft: number;
|
|
22
|
+
keyboardInset: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ViewportEdgeInsets {
|
|
26
|
+
top: number;
|
|
27
|
+
right: number;
|
|
28
|
+
bottom: number;
|
|
29
|
+
left: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface VisibleViewportBounds {
|
|
33
|
+
top: number;
|
|
34
|
+
right: number;
|
|
35
|
+
bottom: number;
|
|
36
|
+
left: number;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ZERO_ENVIRONMENT: ViewportEnvironment = {
|
|
42
|
+
width: 0,
|
|
43
|
+
height: 0,
|
|
44
|
+
offsetTop: 0,
|
|
45
|
+
offsetLeft: 0,
|
|
46
|
+
keyboardInset: 0,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const ZERO_INSETS: ViewportEdgeInsets = {
|
|
50
|
+
top: 0,
|
|
51
|
+
right: 0,
|
|
52
|
+
bottom: 0,
|
|
53
|
+
left: 0,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const VIEWPORT_PROPERTIES = [
|
|
57
|
+
'--pn-viewport-width',
|
|
58
|
+
'--pn-viewport-height',
|
|
59
|
+
'--pn-viewport-offset-top',
|
|
60
|
+
'--pn-viewport-offset-left',
|
|
61
|
+
'--pn-keyboard-inset',
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
64
|
+
type ViewportProperty = (typeof VIEWPORT_PROPERTIES)[number];
|
|
65
|
+
|
|
66
|
+
let subscriberCount = 0;
|
|
67
|
+
let stopObserving: (() => void) | null = null;
|
|
68
|
+
let currentEnvironment: ViewportEnvironment | null = null;
|
|
69
|
+
const environmentListeners = new Set<() => void>();
|
|
70
|
+
|
|
71
|
+
function finiteOr(value: number, fallback: number): number {
|
|
72
|
+
return Number.isFinite(value) ? value : fallback;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function positiveOr(value: number, fallback: number): number {
|
|
76
|
+
const finite = finiteOr(value, fallback);
|
|
77
|
+
return finite > 0 ? finite : fallback;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function rounded(value: number): number {
|
|
81
|
+
return Math.round(value * 100) / 100;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Converts the visual viewport into application-stage geometry. Pinch zoom is
|
|
86
|
+
* intentionally represented by offsets only: reshaping the app while a user
|
|
87
|
+
* zooms and pans would fight accessibility zoom. At the normal scale, browser
|
|
88
|
+
* chrome and the software keyboard are allowed to reduce the usable stage.
|
|
89
|
+
*/
|
|
90
|
+
export function calculateViewportEnvironment({
|
|
91
|
+
layoutWidth,
|
|
92
|
+
layoutHeight,
|
|
93
|
+
visualViewport,
|
|
94
|
+
}: ViewportEnvironmentInput): ViewportEnvironment {
|
|
95
|
+
const safeLayoutWidth = Math.max(0, finiteOr(layoutWidth, 0));
|
|
96
|
+
const safeLayoutHeight = Math.max(0, finiteOr(layoutHeight, 0));
|
|
97
|
+
|
|
98
|
+
if (!visualViewport) {
|
|
99
|
+
return {
|
|
100
|
+
width: rounded(safeLayoutWidth),
|
|
101
|
+
height: rounded(safeLayoutHeight),
|
|
102
|
+
offsetTop: 0,
|
|
103
|
+
offsetLeft: 0,
|
|
104
|
+
keyboardInset: 0,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const scale = positiveOr(visualViewport.scale, 1);
|
|
109
|
+
const offsetTop = Math.max(0, finiteOr(visualViewport.offsetTop, 0));
|
|
110
|
+
const offsetLeft = Math.max(0, finiteOr(visualViewport.offsetLeft, 0));
|
|
111
|
+
const isPinchZoomed = Math.abs(scale - 1) > 0.01;
|
|
112
|
+
if (isPinchZoomed) {
|
|
113
|
+
return {
|
|
114
|
+
width: rounded(safeLayoutWidth),
|
|
115
|
+
height: rounded(safeLayoutHeight),
|
|
116
|
+
offsetTop: rounded(offsetTop),
|
|
117
|
+
offsetLeft: rounded(offsetLeft),
|
|
118
|
+
keyboardInset: 0,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const scaledWidth = positiveOr(visualViewport.width, safeLayoutWidth);
|
|
123
|
+
const scaledHeight = positiveOr(visualViewport.height, safeLayoutHeight);
|
|
124
|
+
const availableWidth = Math.max(0, safeLayoutWidth - offsetLeft);
|
|
125
|
+
const availableHeight = Math.max(0, safeLayoutHeight - offsetTop);
|
|
126
|
+
const width = safeLayoutWidth > 0 ? Math.min(scaledWidth, availableWidth) : scaledWidth;
|
|
127
|
+
const height = safeLayoutHeight > 0 ? Math.min(scaledHeight, availableHeight) : scaledHeight;
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
width: rounded(Math.max(0, width)),
|
|
131
|
+
height: rounded(Math.max(0, height)),
|
|
132
|
+
offsetTop: rounded(offsetTop),
|
|
133
|
+
offsetLeft: rounded(offsetLeft),
|
|
134
|
+
keyboardInset: rounded(Math.max(0, safeLayoutHeight - offsetTop - height)),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Converts the observed viewport into usable fixed-position bounds. Padding
|
|
140
|
+
* and safe-area insets are inputs so positioning remains deterministic and
|
|
141
|
+
* testable instead of reading CSS environment variables in every overlay.
|
|
142
|
+
*/
|
|
143
|
+
export function calculateVisibleViewportBounds(
|
|
144
|
+
environment: ViewportEnvironment,
|
|
145
|
+
edgePadding = 0,
|
|
146
|
+
insets: Partial<ViewportEdgeInsets> = ZERO_INSETS,
|
|
147
|
+
): VisibleViewportBounds {
|
|
148
|
+
const padding = Math.max(0, finiteOr(edgePadding, 0));
|
|
149
|
+
const safeInsets = {
|
|
150
|
+
top: Math.max(0, finiteOr(insets.top ?? 0, 0)),
|
|
151
|
+
right: Math.max(0, finiteOr(insets.right ?? 0, 0)),
|
|
152
|
+
bottom: Math.max(0, finiteOr(insets.bottom ?? 0, 0)),
|
|
153
|
+
left: Math.max(0, finiteOr(insets.left ?? 0, 0)),
|
|
154
|
+
};
|
|
155
|
+
const left = environment.offsetLeft + padding + safeInsets.left;
|
|
156
|
+
const top = environment.offsetTop + padding + safeInsets.top;
|
|
157
|
+
const right = Math.max(
|
|
158
|
+
left,
|
|
159
|
+
environment.offsetLeft + environment.width - padding - safeInsets.right,
|
|
160
|
+
);
|
|
161
|
+
const bottom = Math.max(
|
|
162
|
+
top,
|
|
163
|
+
environment.offsetTop + environment.height - padding - safeInsets.bottom,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
top: rounded(top),
|
|
168
|
+
right: rounded(right),
|
|
169
|
+
bottom: rounded(bottom),
|
|
170
|
+
left: rounded(left),
|
|
171
|
+
width: rounded(Math.max(0, right - left)),
|
|
172
|
+
height: rounded(Math.max(0, bottom - top)),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function shouldUseExpandedComposer({
|
|
177
|
+
bounds,
|
|
178
|
+
coarsePointer,
|
|
179
|
+
}: {
|
|
180
|
+
bounds: VisibleViewportBounds;
|
|
181
|
+
coarsePointer: boolean;
|
|
182
|
+
}): boolean {
|
|
183
|
+
return coarsePointer || bounds.width < 640 || bounds.height < 420;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Returns whether the device's primary pointing input is coarse. */
|
|
187
|
+
export function hasPrimaryCoarsePointer(targetWindow?: Window): boolean {
|
|
188
|
+
const resolvedWindow = targetWindow ?? (typeof window === 'undefined' ? undefined : window);
|
|
189
|
+
if (!resolvedWindow?.matchMedia) return false;
|
|
190
|
+
return resolvedWindow.matchMedia('(pointer: coarse)').matches;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function readViewportEnvironment(targetWindow: Window): ViewportEnvironment {
|
|
194
|
+
const visualViewport = targetWindow.visualViewport;
|
|
195
|
+
return calculateViewportEnvironment({
|
|
196
|
+
layoutWidth: targetWindow.innerWidth,
|
|
197
|
+
layoutHeight: targetWindow.innerHeight,
|
|
198
|
+
visualViewport: visualViewport
|
|
199
|
+
? {
|
|
200
|
+
width: visualViewport.width,
|
|
201
|
+
height: visualViewport.height,
|
|
202
|
+
offsetTop: visualViewport.offsetTop,
|
|
203
|
+
offsetLeft: visualViewport.offsetLeft,
|
|
204
|
+
scale: visualViewport.scale,
|
|
205
|
+
}
|
|
206
|
+
: null,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function environmentsEqual(
|
|
211
|
+
left: ViewportEnvironment | null,
|
|
212
|
+
right: ViewportEnvironment,
|
|
213
|
+
): boolean {
|
|
214
|
+
return !!left
|
|
215
|
+
&& left.width === right.width
|
|
216
|
+
&& left.height === right.height
|
|
217
|
+
&& left.offsetTop === right.offsetTop
|
|
218
|
+
&& left.offsetLeft === right.offsetLeft
|
|
219
|
+
&& left.keyboardInset === right.keyboardInset;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getViewportEnvironmentSnapshot(): ViewportEnvironment {
|
|
223
|
+
if (currentEnvironment) return currentEnvironment;
|
|
224
|
+
if (typeof window === 'undefined') return ZERO_ENVIRONMENT;
|
|
225
|
+
currentEnvironment = readViewportEnvironment(window);
|
|
226
|
+
return currentEnvironment;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function cssValues(environment: ViewportEnvironment): Record<ViewportProperty, string> {
|
|
230
|
+
return {
|
|
231
|
+
'--pn-viewport-width': `${environment.width}px`,
|
|
232
|
+
'--pn-viewport-height': `${environment.height}px`,
|
|
233
|
+
'--pn-viewport-offset-top': `${environment.offsetTop}px`,
|
|
234
|
+
'--pn-viewport-offset-left': `${environment.offsetLeft}px`,
|
|
235
|
+
'--pn-keyboard-inset': `${environment.keyboardInset}px`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function startViewportEnvironmentObserver(
|
|
240
|
+
targetWindow: Window,
|
|
241
|
+
targetDocument: Document,
|
|
242
|
+
): () => void {
|
|
243
|
+
const rootStyle = targetDocument.documentElement.style;
|
|
244
|
+
const previousValues = new Map<ViewportProperty, string>();
|
|
245
|
+
const writtenValues = new Map<ViewportProperty, string>();
|
|
246
|
+
for (const property of VIEWPORT_PROPERTIES) {
|
|
247
|
+
previousValues.set(property, rootStyle.getPropertyValue(property));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let animationFrame: number | null = null;
|
|
251
|
+
|
|
252
|
+
const write = () => {
|
|
253
|
+
animationFrame = null;
|
|
254
|
+
const nextEnvironment = readViewportEnvironment(targetWindow);
|
|
255
|
+
const changed = !environmentsEqual(currentEnvironment, nextEnvironment);
|
|
256
|
+
currentEnvironment = nextEnvironment;
|
|
257
|
+
const nextValues = cssValues(nextEnvironment);
|
|
258
|
+
for (const property of VIEWPORT_PROPERTIES) {
|
|
259
|
+
const nextValue = nextValues[property];
|
|
260
|
+
if (writtenValues.get(property) === nextValue) continue;
|
|
261
|
+
rootStyle.setProperty(property, nextValue);
|
|
262
|
+
writtenValues.set(property, nextValue);
|
|
263
|
+
}
|
|
264
|
+
if (changed) environmentListeners.forEach(listener => listener());
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const scheduleWrite = () => {
|
|
268
|
+
if (animationFrame !== null) return;
|
|
269
|
+
animationFrame = targetWindow.requestAnimationFrame(write);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const visualViewport = targetWindow.visualViewport;
|
|
273
|
+
targetWindow.addEventListener('resize', scheduleWrite);
|
|
274
|
+
targetWindow.addEventListener('orientationchange', scheduleWrite);
|
|
275
|
+
targetWindow.addEventListener('pageshow', scheduleWrite);
|
|
276
|
+
targetDocument.addEventListener('visibilitychange', scheduleWrite);
|
|
277
|
+
visualViewport?.addEventListener('resize', scheduleWrite);
|
|
278
|
+
visualViewport?.addEventListener('scroll', scheduleWrite);
|
|
279
|
+
write();
|
|
280
|
+
|
|
281
|
+
return () => {
|
|
282
|
+
targetWindow.removeEventListener('resize', scheduleWrite);
|
|
283
|
+
targetWindow.removeEventListener('orientationchange', scheduleWrite);
|
|
284
|
+
targetWindow.removeEventListener('pageshow', scheduleWrite);
|
|
285
|
+
targetDocument.removeEventListener('visibilitychange', scheduleWrite);
|
|
286
|
+
visualViewport?.removeEventListener('resize', scheduleWrite);
|
|
287
|
+
visualViewport?.removeEventListener('scroll', scheduleWrite);
|
|
288
|
+
if (animationFrame !== null) targetWindow.cancelAnimationFrame(animationFrame);
|
|
289
|
+
|
|
290
|
+
for (const property of VIEWPORT_PROPERTIES) {
|
|
291
|
+
const previousValue = previousValues.get(property) ?? '';
|
|
292
|
+
if (previousValue) rootStyle.setProperty(property, previousValue);
|
|
293
|
+
else rootStyle.removeProperty(property);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function acquireViewportEnvironment(): () => void {
|
|
299
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return () => {};
|
|
300
|
+
|
|
301
|
+
subscriberCount += 1;
|
|
302
|
+
if (subscriberCount === 1) {
|
|
303
|
+
stopObserving = startViewportEnvironmentObserver(window, document);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let released = false;
|
|
307
|
+
return () => {
|
|
308
|
+
if (released) return;
|
|
309
|
+
released = true;
|
|
310
|
+
subscriberCount = Math.max(0, subscriberCount - 1);
|
|
311
|
+
if (subscriberCount !== 0) return;
|
|
312
|
+
stopObserving?.();
|
|
313
|
+
stopObserving = null;
|
|
314
|
+
currentEnvironment = null;
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function subscribeViewportEnvironment(listener: () => void): () => void {
|
|
319
|
+
environmentListeners.add(listener);
|
|
320
|
+
const release = acquireViewportEnvironment();
|
|
321
|
+
return () => {
|
|
322
|
+
environmentListeners.delete(listener);
|
|
323
|
+
release();
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Keeps Plannotator's shared viewport CSS properties synchronized without
|
|
329
|
+
* putting high-frequency browser geometry into React state.
|
|
330
|
+
*/
|
|
331
|
+
export function useViewportEnvironment(): void {
|
|
332
|
+
useEffect(() => acquireViewportEnvironment(), []);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Reactive bounds for fixed overlays. It shares the root observer and its
|
|
337
|
+
* animation-frame coalescing, so composer consumers do not add parallel
|
|
338
|
+
* Visual Viewport listeners.
|
|
339
|
+
*/
|
|
340
|
+
export function useVisibleViewportBounds(edgePadding = 0): VisibleViewportBounds {
|
|
341
|
+
const environment = useSyncExternalStore(
|
|
342
|
+
subscribeViewportEnvironment,
|
|
343
|
+
getViewportEnvironmentSnapshot,
|
|
344
|
+
() => ZERO_ENVIRONMENT,
|
|
345
|
+
);
|
|
346
|
+
return useMemo(
|
|
347
|
+
() => calculateVisibleViewportBounds(environment, edgePadding),
|
|
348
|
+
[edgePadding, environment],
|
|
349
|
+
);
|
|
350
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plannotator/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./components/*": "./components/*.tsx",
|
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
"./components/goal-setup/*": "./components/goal-setup/*.tsx",
|
|
11
11
|
"./components/ImageAnnotator": "./components/ImageAnnotator/index.tsx",
|
|
12
12
|
"./components/html-viewer": "./components/html-viewer/index.ts",
|
|
13
|
+
"./components/html-viewer/bridge-script": "./components/html-viewer/bridge-script.ts",
|
|
13
14
|
"./components/sidebar/*": "./components/sidebar/*.tsx",
|
|
14
15
|
"./components/plan-diff/*": "./components/plan-diff/*.tsx",
|
|
15
16
|
"./utils/*": "./utils/*.ts",
|
|
16
17
|
"./lib/*": "./lib/*.ts",
|
|
17
18
|
"./hooks/*": "./hooks/*.ts",
|
|
18
19
|
"./shortcuts": "./shortcuts/index.ts",
|
|
20
|
+
"./webmcp": "./webmcp/index.ts",
|
|
19
21
|
"./config": "./config/index.ts",
|
|
20
22
|
"./configure": "./configure.ts",
|
|
21
23
|
"./theme-modes": "./components/themeModes.tsx",
|
|
@@ -31,6 +33,7 @@
|
|
|
31
33
|
"lib",
|
|
32
34
|
"shortcuts",
|
|
33
35
|
"utils",
|
|
36
|
+
"webmcp",
|
|
34
37
|
"assets",
|
|
35
38
|
"themes",
|
|
36
39
|
"sprite_package_additional",
|
|
@@ -68,7 +71,7 @@
|
|
|
68
71
|
"@lezer/highlight": "^1.2.3",
|
|
69
72
|
"@pierre/diffs": "1.3.2",
|
|
70
73
|
"@plannotator/atomic-editor": "^0.8.0",
|
|
71
|
-
"@plannotator/core": "0.
|
|
74
|
+
"@plannotator/core": "0.25.0",
|
|
72
75
|
"@plannotator/markdown-editor": "^0.4.0",
|
|
73
76
|
"@plannotator/web-highlighter": "^0.8.1",
|
|
74
77
|
"@tanstack/react-table": "^8.21.3",
|
package/shortcuts/index.ts
CHANGED
|
@@ -2,12 +2,15 @@ export * from './core';
|
|
|
2
2
|
export * from './runtime';
|
|
3
3
|
|
|
4
4
|
// plan-review scopes
|
|
5
|
+
export { annotationModeShortcuts, useAnnotationModeShortcuts } from './plan-review/annotationMode.shortcuts';
|
|
5
6
|
export { annotationToolbarShortcuts, useAnnotationToolbarShortcuts } from './plan-review/annotationToolbar.shortcuts';
|
|
6
7
|
export { annotationPanelShortcuts, useAnnotationPanelShortcuts } from './plan-review/annotationPanel.shortcuts';
|
|
7
8
|
export { commentPopoverShortcuts } from './plan-review/commentPopover.shortcuts';
|
|
8
9
|
export { imageAnnotatorShortcuts, useImageAnnotatorShortcuts } from './plan-review/imageAnnotator.shortcuts';
|
|
9
10
|
export { inputMethodShortcuts } from './plan-review/inputMethod.shortcuts';
|
|
11
|
+
export { htmlAnnotateShortcuts, useHtmlAnnotateShortcuts } from './plan-review/htmlAnnotate.shortcuts';
|
|
10
12
|
export { viewerShortcuts, useViewerShortcuts } from './plan-review/viewer.shortcuts';
|
|
13
|
+
export { documentViewShortcuts, useDocumentViewShortcuts } from './plan-review/documentView.shortcuts';
|
|
11
14
|
export {
|
|
12
15
|
describeVimSelectionAction,
|
|
13
16
|
isVimSelectionActionId,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { defineShortcutScope } from '../core';
|
|
2
|
+
import {
|
|
3
|
+
useShortcutScope,
|
|
4
|
+
type ShortcutHandlers,
|
|
5
|
+
type UseShortcutScopeOptions,
|
|
6
|
+
} from '../runtime';
|
|
7
|
+
|
|
8
|
+
// Shift+digit rather than mnemonic letters: bare letters are swallowed by
|
|
9
|
+
// type-to-comment, bare digits by the label picker, Mod+digit by the browser's
|
|
10
|
+
// tab switching, and Mod+Shift+3/4 by macOS screenshots. Follows the toolstrip
|
|
11
|
+
// left to right.
|
|
12
|
+
export const annotationModeShortcuts = defineShortcutScope({
|
|
13
|
+
id: 'annotation-mode',
|
|
14
|
+
title: 'Annotation Mode',
|
|
15
|
+
shortcuts: {
|
|
16
|
+
selectMarkupMode: {
|
|
17
|
+
description: 'Markup mode',
|
|
18
|
+
bindings: ['Shift+1'],
|
|
19
|
+
section: 'Annotations',
|
|
20
|
+
hint: 'Selecting text opens the annotation toolbar.',
|
|
21
|
+
preventDefault: true,
|
|
22
|
+
displayOrder: 1,
|
|
23
|
+
},
|
|
24
|
+
selectCommentMode: {
|
|
25
|
+
description: 'Comment mode',
|
|
26
|
+
bindings: ['Shift+2'],
|
|
27
|
+
section: 'Annotations',
|
|
28
|
+
hint: 'Selecting text opens the comment editor.',
|
|
29
|
+
preventDefault: true,
|
|
30
|
+
displayOrder: 2,
|
|
31
|
+
},
|
|
32
|
+
selectRedlineMode: {
|
|
33
|
+
description: 'Redline mode',
|
|
34
|
+
bindings: ['Shift+3'],
|
|
35
|
+
section: 'Annotations',
|
|
36
|
+
hint: 'Selecting text marks it for deletion.',
|
|
37
|
+
preventDefault: true,
|
|
38
|
+
displayOrder: 3,
|
|
39
|
+
},
|
|
40
|
+
selectQuickLabelMode: {
|
|
41
|
+
description: 'Label mode',
|
|
42
|
+
bindings: ['Shift+4'],
|
|
43
|
+
section: 'Annotations',
|
|
44
|
+
hint: 'Selecting text opens the quick label picker.',
|
|
45
|
+
preventDefault: true,
|
|
46
|
+
displayOrder: 4,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// --- Type-to-comment capture guard (#1244 follow-up) ---
|
|
52
|
+
// Shift+1..4 produce printable characters (! @ # $). While a surface's
|
|
53
|
+
// type-to-comment listener owns printable keys (a selection exists and the
|
|
54
|
+
// annotation toolbar is open), those characters belong to the comment being
|
|
55
|
+
// started: a mode shortcut firing there both eats the character and silently
|
|
56
|
+
// re-arms the mode (Shift+3 arming Redline mid-thought). The toolbar
|
|
57
|
+
// registers its capture here; the scope hook refuses to dispatch while any
|
|
58
|
+
// capture is held.
|
|
59
|
+
let typeToCommentCaptures = 0;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Held by AnnotationToolbar for the lifetime of its type-to-comment keydown
|
|
63
|
+
* listener. Returns a release function; releasing more than once is safe.
|
|
64
|
+
*/
|
|
65
|
+
export function acquireTypeToCommentCapture(): () => void {
|
|
66
|
+
typeToCommentCaptures += 1;
|
|
67
|
+
let released = false;
|
|
68
|
+
return () => {
|
|
69
|
+
if (released) return;
|
|
70
|
+
released = true;
|
|
71
|
+
typeToCommentCaptures -= 1;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type AnnotationModeScope = typeof annotationModeShortcuts;
|
|
76
|
+
type AnnotationModeOptions = Omit<UseShortcutScopeOptions<AnnotationModeScope>, 'scope'>;
|
|
77
|
+
|
|
78
|
+
export function useAnnotationModeShortcuts(options: AnnotationModeOptions): void {
|
|
79
|
+
const guarded: ShortcutHandlers<AnnotationModeScope> = {};
|
|
80
|
+
for (const actionId of Object.keys(options.handlers) as Array<keyof typeof options.handlers>) {
|
|
81
|
+
const handler = options.handlers[actionId];
|
|
82
|
+
if (!handler) continue;
|
|
83
|
+
const config = typeof handler === 'function' ? { handle: handler } : handler;
|
|
84
|
+
guarded[actionId] = {
|
|
85
|
+
...config,
|
|
86
|
+
when: (event: KeyboardEvent) =>
|
|
87
|
+
typeToCommentCaptures === 0 && (config.when ? config.when(event) : true),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
useShortcutScope({ scope: annotationModeShortcuts, ...options, handlers: guarded });
|
|
91
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineShortcutScope } from '../core';
|
|
2
|
+
import { createShortcutScopeHook } from '../runtime';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Document-chrome view commands shared by plan review and annotate mode.
|
|
6
|
+
*
|
|
7
|
+
* Focus mode is the keyboard entry point to the same view state the document
|
|
8
|
+
* card's `Focus` control drives: both side panels collapse in one press and the
|
|
9
|
+
* previous arrangement comes back on the next one.
|
|
10
|
+
*/
|
|
11
|
+
export const documentViewShortcuts = defineShortcutScope({
|
|
12
|
+
id: 'document-view',
|
|
13
|
+
title: 'Document View',
|
|
14
|
+
shortcuts: {
|
|
15
|
+
toggleFocusMode: {
|
|
16
|
+
description: 'Toggle focus mode',
|
|
17
|
+
bindings: ['Mod+.'],
|
|
18
|
+
section: 'View',
|
|
19
|
+
hint: 'Collapses the Contents sidebar and the right-hand panel together; press again to restore whatever was open before.',
|
|
20
|
+
displayOrder: 10,
|
|
21
|
+
preventDefault: true,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const useDocumentViewShortcuts = createShortcutScopeHook(documentViewShortcuts);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineShortcutScope } from '../core';
|
|
2
|
+
import { createShortcutScopeHook } from '../runtime';
|
|
3
|
+
|
|
4
|
+
// Interact/Annotate toggle for HTML and live-app annotate surfaces. The same
|
|
5
|
+
// chord is mirrored inside the sandboxed iframe by the bridge (focus usually
|
|
6
|
+
// lives in there on live apps) and forwarded to the parent, so it works
|
|
7
|
+
// regardless of which document owns the keyboard. No bare-letter binding:
|
|
8
|
+
// bare letters belong to type-to-comment and the page itself.
|
|
9
|
+
export const htmlAnnotateShortcuts = defineShortcutScope({
|
|
10
|
+
id: 'html-annotate',
|
|
11
|
+
title: 'HTML Annotate',
|
|
12
|
+
shortcuts: {
|
|
13
|
+
toggleAnnotateMode: {
|
|
14
|
+
description: 'Toggle Interact / Annotate',
|
|
15
|
+
bindings: ['Mod+Shift+A'],
|
|
16
|
+
section: 'Annotations',
|
|
17
|
+
hint: 'On HTML and live app surfaces: arm annotation capture, or hand clicks back to the page. Esc also exits Annotate.',
|
|
18
|
+
preventDefault: true,
|
|
19
|
+
displayOrder: 0,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const useHtmlAnnotateShortcuts = createShortcutScopeHook(htmlAnnotateShortcuts);
|