agentation-vue 0.2.6 → 0.2.12
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 +94 -0
- package/dist/AgentationVue.vue +270 -60
- package/dist/components/AgentationToolbar.d.vue.ts +2 -0
- package/dist/components/AgentationToolbar.vue +12 -3
- package/dist/components/AgentationToolbar.vue.d.ts +2 -0
- package/dist/components/AnnotationInput.d.vue.ts +2 -0
- package/dist/components/AnnotationInput.vue +88 -16
- package/dist/components/AnnotationInput.vue.d.ts +2 -0
- package/dist/components/AnnotationMarker.d.vue.ts +1 -0
- package/dist/components/AnnotationMarker.vue +12 -3
- package/dist/components/AnnotationMarker.vue.d.ts +1 -0
- package/dist/components/MentionDropdown.d.vue.ts +16 -0
- package/dist/components/MentionDropdown.vue +41 -0
- package/dist/components/MentionDropdown.vue.d.ts +16 -0
- package/dist/components/SettingsPanel.vue +26 -3
- package/dist/components/SettingsPopover.vue +3 -5
- package/dist/composables/useAnnotations.d.ts +2 -0
- package/dist/composables/useAnnotations.mjs +6 -1
- package/dist/composables/useKeyboardShortcuts.mjs +22 -8
- package/dist/composables/useMentionDropdown.d.ts +21 -0
- package/dist/composables/useMentionDropdown.mjs +162 -0
- package/dist/composables/useOutputFormatter.mjs +2 -1
- package/dist/composables/usePeekMode.d.ts +14 -0
- package/dist/composables/usePeekMode.mjs +119 -0
- package/dist/composables/useSettings.d.ts +1 -0
- package/dist/composables/useSettings.mjs +2 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.mjs +3 -0
- package/dist/styles/agentation.css +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/utils/mention.d.ts +13 -0
- package/dist/utils/mention.mjs +46 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# agentation-vue
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/agentation-vue)
|
|
4
|
+
[](https://www.npmjs.com/package/agentation-vue)
|
|
5
|
+
|
|
6
|
+
Visual feedback tool for AI coding agents. Drop a single component into your Vue app and let your AI agent see and annotate UI elements.
|
|
7
|
+
|
|
8
|
+
Compatible with **Vue 3.3+** and **Vue 2.7**.
|
|
9
|
+
|
|
10
|
+
> **Note:** This is an unofficial, community-maintained Vue adaptation of [Agentation](https://github.com/benjitaylor/agentation). It is not affiliated with or endorsed by the original project.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install agentation-vue
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Setup — Vue 3
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { AgentationVuePlugin } from 'agentation-vue'
|
|
22
|
+
// main.ts
|
|
23
|
+
import { createApp } from 'vue'
|
|
24
|
+
import App from './App.vue'
|
|
25
|
+
import 'agentation-vue/style.css'
|
|
26
|
+
|
|
27
|
+
createApp(App)
|
|
28
|
+
.use(AgentationVuePlugin)
|
|
29
|
+
.mount('#app')
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then place the component once in your root `App.vue`:
|
|
33
|
+
|
|
34
|
+
```vue
|
|
35
|
+
<template>
|
|
36
|
+
<router-view />
|
|
37
|
+
<agentation-vue />
|
|
38
|
+
</template>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Setup — Vue 2.7
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { AgentationVuePlugin } from 'agentation-vue'
|
|
45
|
+
// main.ts
|
|
46
|
+
import Vue from 'vue'
|
|
47
|
+
import App from './App.vue'
|
|
48
|
+
import 'agentation-vue/style.css'
|
|
49
|
+
|
|
50
|
+
Vue.use(AgentationVuePlugin)
|
|
51
|
+
|
|
52
|
+
new Vue({
|
|
53
|
+
render: h => h(App),
|
|
54
|
+
}).$mount('#app')
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Then place the component once in your root `App.vue`:
|
|
58
|
+
|
|
59
|
+
```vue
|
|
60
|
+
<template>
|
|
61
|
+
<div>
|
|
62
|
+
<router-view />
|
|
63
|
+
<agentation-vue />
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> Vue 2 templates require a single root element — wrap your content in a `<div>`.
|
|
69
|
+
|
|
70
|
+
## Chrome extension
|
|
71
|
+
|
|
72
|
+
This repo also contains a Chrome extension for injecting Agentation without modifying the target app code.
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
- **Click-to-annotate** — click any element to pin a numbered marker and leave a comment
|
|
77
|
+
- **Multi-select** — `Shift+drag` to rubber-band select multiple elements at once
|
|
78
|
+
- **Area select** — `Alt+drag` to annotate a screen region
|
|
79
|
+
- **Text selection** — highlight text to annotate it
|
|
80
|
+
- **Vue component tree** — automatically detects and reports the Vue component hierarchy for each annotated element
|
|
81
|
+
- **Markdown output** — copies all annotations as structured Markdown, ready to paste into an AI chat
|
|
82
|
+
- **Forensic mode** — captures bounding boxes, computed styles, CSS classes, and accessibility attributes
|
|
83
|
+
- **Themes** — light, dark, or auto (follows system preference)
|
|
84
|
+
- **Auto-hide launcher** — optionally tucks the collapsed floating button near screen edges and reveals it on approach
|
|
85
|
+
- **Session persistence** — annotations survive refreshes and are scoped per page URL via `sessionStorage`
|
|
86
|
+
- **Custom tooltip directive** — `v-va-tooltip` with optional keyboard shortcut badge
|
|
87
|
+
|
|
88
|
+
## Acknowledgments
|
|
89
|
+
|
|
90
|
+
This project is inspired by and based on [Agentation](https://github.com/benjitaylor/agentation) by [Ben Taylor](https://github.com/benjitaylor). Thank you for the original concept and implementation.
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
[PolyForm Shield 1.0.0](./LICENSE) — Copyright (c) 2026 Dorian Becker
|
package/dist/AgentationVue.vue
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isVue2 as _isVue2,
|
|
4
|
+
computed,
|
|
5
|
+
defineComponent,
|
|
6
|
+
onBeforeUnmount,
|
|
7
|
+
onMounted,
|
|
8
|
+
ref,
|
|
9
|
+
watch
|
|
10
|
+
} from "vue-demi";
|
|
3
11
|
import AgentationToolbar from "./components/AgentationToolbar.vue";
|
|
4
12
|
import AnnotationInput from "./components/AnnotationInput.vue";
|
|
5
13
|
import AnnotationMarker from "./components/AnnotationMarker.vue";
|
|
@@ -10,15 +18,28 @@ import { useAnnotations } from "./composables/useAnnotations.mjs";
|
|
|
10
18
|
import { useAreaSelect } from "./composables/useAreaSelect.mjs";
|
|
11
19
|
import { useElementDetection } from "./composables/useElementDetection.mjs";
|
|
12
20
|
import { useInteractionMode } from "./composables/useInteractionMode.mjs";
|
|
13
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_SHORTCUT_CONFIG,
|
|
23
|
+
useKeyboardShortcuts
|
|
24
|
+
} from "./composables/useKeyboardShortcuts.mjs";
|
|
14
25
|
import { useMarkerPositions } from "./composables/useMarkerPositions.mjs";
|
|
15
26
|
import { useMultiSelect } from "./composables/useMultiSelect.mjs";
|
|
16
27
|
import { useOutputFormatter } from "./composables/useOutputFormatter.mjs";
|
|
28
|
+
import { usePeekMode } from "./composables/usePeekMode.mjs";
|
|
17
29
|
import { useSettings } from "./composables/useSettings.mjs";
|
|
18
30
|
import { useTextSelection } from "./composables/useTextSelection.mjs";
|
|
31
|
+
import { PEEK_HOLD_DURATION_MS } from "./constants.mjs";
|
|
19
32
|
import { isInsideAgentationTree } from "./utils/agentation-tree.mjs";
|
|
20
33
|
import { copyToClipboard } from "./utils/clipboard.mjs";
|
|
21
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
isFixed as checkIsFixed,
|
|
36
|
+
detectVueComponents,
|
|
37
|
+
getAccessibilityInfo,
|
|
38
|
+
getComputedStylesSummary,
|
|
39
|
+
getNearbyElements,
|
|
40
|
+
getNearbyText,
|
|
41
|
+
getRelevantComputedStyles
|
|
42
|
+
} from "./utils/dom-inspector.mjs";
|
|
22
43
|
import { createPortalContainer, destroyPortalContainer } from "./utils/portal.mjs";
|
|
23
44
|
import { getElementName, getElementPath } from "./utils/selectors.mjs";
|
|
24
45
|
import { boundingBoxToStyle } from "./utils/style.mjs";
|
|
@@ -60,14 +81,49 @@ const toolbarRef = ref(null);
|
|
|
60
81
|
const currentUrl = ref(props.pageUrl || getCurrentUrl());
|
|
61
82
|
const { settings } = useSettings();
|
|
62
83
|
const { mode, transition } = useInteractionMode();
|
|
63
|
-
const {
|
|
64
|
-
|
|
84
|
+
const {
|
|
85
|
+
annotations,
|
|
86
|
+
addAnnotation,
|
|
87
|
+
removeAnnotation,
|
|
88
|
+
updateAnnotation,
|
|
89
|
+
clearAnnotations,
|
|
90
|
+
restoreAnnotations,
|
|
91
|
+
setScopeUrl
|
|
92
|
+
} = useAnnotations(currentUrl.value);
|
|
93
|
+
const {
|
|
94
|
+
hoveredRect,
|
|
95
|
+
hoveredName,
|
|
96
|
+
hoveredComponentChain,
|
|
97
|
+
onMouseMove,
|
|
98
|
+
clearHighlight,
|
|
99
|
+
getElementUnderOverlay,
|
|
100
|
+
cleanup: cleanupDetection
|
|
101
|
+
} = useElementDetection(overlayEl, () => settings.showComponentTree);
|
|
65
102
|
const textSelection = useTextSelection(mode);
|
|
66
103
|
const multiSelect = useMultiSelect(mode, transition);
|
|
67
104
|
const areaSelect = useAreaSelect(mode, transition);
|
|
68
105
|
const animPause = useAnimationPause();
|
|
69
106
|
const { recalculatePositions: _recalculatePositions } = useMarkerPositions(annotations);
|
|
70
107
|
const { formatAnnotations } = useOutputFormatter();
|
|
108
|
+
const peekActive = ref(false);
|
|
109
|
+
const peekMode = usePeekMode({
|
|
110
|
+
peekKey: () => settings.peekKey,
|
|
111
|
+
enabled: () => mode.value === "idle" && (!toolbarRef.value || !toolbarRef.value.expanded),
|
|
112
|
+
isInputOpen: () => mode.value === "input-open",
|
|
113
|
+
onActivate() {
|
|
114
|
+
peekActive.value = true;
|
|
115
|
+
transition("inspect");
|
|
116
|
+
},
|
|
117
|
+
onDeactivate() {
|
|
118
|
+
peekActive.value = false;
|
|
119
|
+
if (mode.value === "input-open")
|
|
120
|
+
return;
|
|
121
|
+
if (mode.value !== "idle") {
|
|
122
|
+
transition("idle");
|
|
123
|
+
clearHighlight();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
71
127
|
const pendingPosition = ref(null);
|
|
72
128
|
const pendingElementName = ref("");
|
|
73
129
|
const pendingTarget = ref(null);
|
|
@@ -78,11 +134,17 @@ const editingAnnotation = ref(null);
|
|
|
78
134
|
const settingsOpen = ref(false);
|
|
79
135
|
const settingsAnchorEl = ref(null);
|
|
80
136
|
const copyFeedback = ref(false);
|
|
137
|
+
const undoFeedback = ref(false);
|
|
138
|
+
const undoSnapshot = ref([]);
|
|
139
|
+
let undoTimer = null;
|
|
140
|
+
const UNDO_TIMEOUT_MS = 5e3;
|
|
81
141
|
const toolbarDragging = ref(false);
|
|
82
142
|
const DRAG_END_SUPPRESSION_MS = 500;
|
|
83
143
|
const SETTINGS_CLOSE_SUPPRESSION_MS = 220;
|
|
84
144
|
let suppressInteractionsUntil = 0;
|
|
85
|
-
const effectiveBlockPageInteractions = computed(
|
|
145
|
+
const effectiveBlockPageInteractions = computed(
|
|
146
|
+
() => props.blockPageInteractions ?? settings.blockPageInteractions
|
|
147
|
+
);
|
|
86
148
|
const rootStyle = computed(() => {
|
|
87
149
|
const hex = settings.markerColor;
|
|
88
150
|
if (!hex)
|
|
@@ -106,7 +168,16 @@ const pendingMarkerY = computed(() => {
|
|
|
106
168
|
return 0;
|
|
107
169
|
return pendingPosition.value.y + (window.scrollY || document.documentElement.scrollTop);
|
|
108
170
|
});
|
|
109
|
-
const pendingIsSelection = computed(
|
|
171
|
+
const pendingIsSelection = computed(
|
|
172
|
+
() => mode.value === "input-open" && !editingAnnotation.value && (multiSelect.selectedElements.value.length > 0 || !!areaSelect.areaRect.value)
|
|
173
|
+
);
|
|
174
|
+
const mentionCandidates = computed(
|
|
175
|
+
() => annotations.value.map((ann, i) => ({
|
|
176
|
+
id: ann.id,
|
|
177
|
+
displayNumber: i + 1,
|
|
178
|
+
commentPreview: ann.comment.replace(/@\[\d+\]/g, "@\u2026").slice(0, 40) + (ann.comment.length > 40 ? "\u2026" : "")
|
|
179
|
+
})).filter((c) => !editingAnnotation.value || c.id !== editingAnnotation.value.id)
|
|
180
|
+
);
|
|
110
181
|
let portalContainer = null;
|
|
111
182
|
const isVue2 = _isVue2;
|
|
112
183
|
const PassThrough = defineComponent({
|
|
@@ -115,8 +186,12 @@ const PassThrough = defineComponent({
|
|
|
115
186
|
return (typeof slot === "function" ? slot() : slot?.[0]) || null;
|
|
116
187
|
}
|
|
117
188
|
});
|
|
118
|
-
const portalWrapper = computed(
|
|
119
|
-
|
|
189
|
+
const portalWrapper = computed(
|
|
190
|
+
() => props.disablePortal || isVue2 ? PassThrough : "Teleport"
|
|
191
|
+
);
|
|
192
|
+
const portalProps = computed(
|
|
193
|
+
() => props.disablePortal || isVue2 ? {} : { to: "body" }
|
|
194
|
+
);
|
|
120
195
|
onMounted(() => {
|
|
121
196
|
if (!props.disablePortal && isVue2 && rootEl.value) {
|
|
122
197
|
portalContainer = createPortalContainer();
|
|
@@ -124,39 +199,79 @@ onMounted(() => {
|
|
|
124
199
|
}
|
|
125
200
|
});
|
|
126
201
|
onBeforeUnmount(() => {
|
|
202
|
+
dismissUndo();
|
|
127
203
|
animPause.cleanup();
|
|
128
204
|
cleanupDetection();
|
|
129
205
|
if (portalContainer) {
|
|
130
206
|
destroyPortalContainer(portalContainer);
|
|
131
207
|
}
|
|
132
208
|
});
|
|
133
|
-
watch(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
watch(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
watch(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
209
|
+
watch(
|
|
210
|
+
() => props.outputDetail,
|
|
211
|
+
(v) => {
|
|
212
|
+
if (v)
|
|
213
|
+
settings.outputDetail = v;
|
|
214
|
+
},
|
|
215
|
+
{ immediate: true }
|
|
216
|
+
);
|
|
217
|
+
watch(
|
|
218
|
+
() => props.markerColor,
|
|
219
|
+
(v) => {
|
|
220
|
+
if (v)
|
|
221
|
+
settings.markerColor = v;
|
|
222
|
+
},
|
|
223
|
+
{ immediate: true }
|
|
224
|
+
);
|
|
225
|
+
watch(
|
|
226
|
+
() => props.theme,
|
|
227
|
+
(v) => {
|
|
228
|
+
if (v)
|
|
229
|
+
settings.theme = v;
|
|
230
|
+
},
|
|
231
|
+
{ immediate: true }
|
|
232
|
+
);
|
|
233
|
+
watch(
|
|
234
|
+
() => props.blockPageInteractions,
|
|
235
|
+
(v) => {
|
|
236
|
+
if (v)
|
|
237
|
+
settings.blockPageInteractions = v;
|
|
238
|
+
},
|
|
239
|
+
{ immediate: true }
|
|
240
|
+
);
|
|
241
|
+
watch(
|
|
242
|
+
() => props.autoHideToolbar,
|
|
243
|
+
(v) => {
|
|
244
|
+
if (v)
|
|
245
|
+
settings.autoHideToolbar = v;
|
|
246
|
+
},
|
|
247
|
+
{ immediate: true }
|
|
248
|
+
);
|
|
249
|
+
watch(
|
|
250
|
+
() => props.pageUrl,
|
|
251
|
+
(url) => {
|
|
252
|
+
syncUrlScope(url || getCurrentUrl());
|
|
253
|
+
},
|
|
254
|
+
{ immediate: true }
|
|
255
|
+
);
|
|
256
|
+
watch(
|
|
257
|
+
() => props.activationKey,
|
|
258
|
+
(v) => {
|
|
259
|
+
if (v !== void 0)
|
|
260
|
+
settings.activationKey = v;
|
|
261
|
+
},
|
|
262
|
+
{ immediate: true }
|
|
263
|
+
);
|
|
264
|
+
let crosshairStyle = null;
|
|
265
|
+
watch(mode, (current, previous) => {
|
|
266
|
+
if (current !== "idle" && previous === "idle") {
|
|
267
|
+
crosshairStyle = document.createElement("style");
|
|
268
|
+
crosshairStyle.textContent = "* { cursor: crosshair !important; }";
|
|
269
|
+
document.head.appendChild(crosshairStyle);
|
|
270
|
+
} else if (current === "idle" && previous !== "idle") {
|
|
271
|
+
crosshairStyle?.remove();
|
|
272
|
+
crosshairStyle = null;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
160
275
|
function onActivate() {
|
|
161
276
|
transition("inspect");
|
|
162
277
|
}
|
|
@@ -179,8 +294,6 @@ function onOverlayMouseMove(e) {
|
|
|
179
294
|
function onOverlayMouseDown(e) {
|
|
180
295
|
if (isInteractionLocked())
|
|
181
296
|
return;
|
|
182
|
-
if (multiSelect.onMouseDown(e))
|
|
183
|
-
return;
|
|
184
297
|
areaSelect.onMouseDown(e);
|
|
185
298
|
}
|
|
186
299
|
function onOverlayMouseUp(e) {
|
|
@@ -207,7 +320,10 @@ function onOverlayMouseUp(e) {
|
|
|
207
320
|
areaSelect.onMouseUp();
|
|
208
321
|
const rect = areaSelect.areaRect.value;
|
|
209
322
|
if (rect && rect.width > 10 && rect.height > 10) {
|
|
210
|
-
pendingPosition.value = {
|
|
323
|
+
pendingPosition.value = {
|
|
324
|
+
x: rect.x + rect.width / 2,
|
|
325
|
+
y: rect.y + rect.height / 2
|
|
326
|
+
};
|
|
211
327
|
pendingElementName.value = "Area selection";
|
|
212
328
|
pendingComponentChain.value = void 0;
|
|
213
329
|
pendingComputedStyles.value = void 0;
|
|
@@ -229,7 +345,9 @@ function onOverlayMouseUp(e) {
|
|
|
229
345
|
};
|
|
230
346
|
pendingElementName.value = `"${textResult.selectedText.slice(0, 30)}"`;
|
|
231
347
|
pendingComponentChain.value = getVueComponents(textResult.anchorElement);
|
|
232
|
-
pendingComputedStyles.value = getRelevantComputedStyles(
|
|
348
|
+
pendingComputedStyles.value = getRelevantComputedStyles(
|
|
349
|
+
textResult.anchorElement
|
|
350
|
+
);
|
|
233
351
|
pendingTarget.value = textResult.anchorElement;
|
|
234
352
|
pendingTextSelection.value = {
|
|
235
353
|
text: textResult.selectedText,
|
|
@@ -282,7 +400,10 @@ function shouldUseDocumentFallbackEvents() {
|
|
|
282
400
|
return mode.value === "inspect" && !effectiveBlockPageInteractions.value && !isInteractionLocked();
|
|
283
401
|
}
|
|
284
402
|
function lockInteractionsTemporarily(durationMs) {
|
|
285
|
-
suppressInteractionsUntil = Math.max(
|
|
403
|
+
suppressInteractionsUntil = Math.max(
|
|
404
|
+
suppressInteractionsUntil,
|
|
405
|
+
Date.now() + durationMs
|
|
406
|
+
);
|
|
286
407
|
}
|
|
287
408
|
function isInteractionLocked() {
|
|
288
409
|
return settingsOpen.value || toolbarDragging.value || Date.now() < suppressInteractionsUntil;
|
|
@@ -297,6 +418,7 @@ function closeSettings(lockInteractions = true) {
|
|
|
297
418
|
function syncUrlScope(nextUrl) {
|
|
298
419
|
if (!nextUrl || currentUrl.value === nextUrl)
|
|
299
420
|
return;
|
|
421
|
+
dismissUndo();
|
|
300
422
|
currentUrl.value = nextUrl;
|
|
301
423
|
setScopeUrl(nextUrl);
|
|
302
424
|
}
|
|
@@ -395,7 +517,9 @@ function onInputAdd(comment) {
|
|
|
395
517
|
comment,
|
|
396
518
|
url,
|
|
397
519
|
element: "multi",
|
|
398
|
-
elementPath: `region at (${Math.round(boundingBox.x)}, ${Math.round(
|
|
520
|
+
elementPath: `region at (${Math.round(boundingBox.x)}, ${Math.round(
|
|
521
|
+
boundingBox.y
|
|
522
|
+
)})`,
|
|
399
523
|
isMultiSelect: true,
|
|
400
524
|
elements,
|
|
401
525
|
boundingBox,
|
|
@@ -471,7 +595,12 @@ function onInputAdd(comment) {
|
|
|
471
595
|
vueComponents: getVueComponents(el),
|
|
472
596
|
nearbyElements: getNearbyElements(el),
|
|
473
597
|
nearbyText: getNearbyText(el),
|
|
474
|
-
boundingBox: {
|
|
598
|
+
boundingBox: {
|
|
599
|
+
x: rect.x,
|
|
600
|
+
y: rect.y,
|
|
601
|
+
width: rect.width,
|
|
602
|
+
height: rect.height
|
|
603
|
+
},
|
|
475
604
|
cssClasses: detail === "forensic" ? Array.from(el.classList).join(" ") : void 0,
|
|
476
605
|
fullPath: detail === "forensic" ? getElementPath(el) : void 0,
|
|
477
606
|
computedStyles: detail === "forensic" ? getComputedStylesSummary(el) : void 0,
|
|
@@ -480,16 +609,31 @@ function onInputAdd(comment) {
|
|
|
480
609
|
emit("annotation-add", ann);
|
|
481
610
|
}
|
|
482
611
|
resetPendingState();
|
|
483
|
-
|
|
612
|
+
clearHighlight();
|
|
613
|
+
if (peekActive.value) {
|
|
614
|
+
transition("inspect");
|
|
615
|
+
peekMode.scheduleExit();
|
|
616
|
+
} else {
|
|
617
|
+
transition("inspect");
|
|
618
|
+
}
|
|
484
619
|
}
|
|
485
620
|
function onInputCancel() {
|
|
486
621
|
resetPendingState();
|
|
487
|
-
multiSelect.reset();
|
|
488
622
|
areaSelect.reset();
|
|
489
|
-
|
|
623
|
+
clearHighlight();
|
|
624
|
+
if (peekActive.value) {
|
|
625
|
+
transition("idle");
|
|
626
|
+
peekMode.deactivate();
|
|
627
|
+
} else {
|
|
628
|
+
transition("inspect");
|
|
629
|
+
}
|
|
490
630
|
}
|
|
491
631
|
async function onCopy() {
|
|
492
|
-
const markdown = formatAnnotations(
|
|
632
|
+
const markdown = formatAnnotations(
|
|
633
|
+
annotations.value,
|
|
634
|
+
settings.outputDetail,
|
|
635
|
+
resolvedUrl.value
|
|
636
|
+
);
|
|
493
637
|
if (props.copyToClipboard !== false) {
|
|
494
638
|
const success = await copyToClipboard(markdown);
|
|
495
639
|
if (!success)
|
|
@@ -501,13 +645,37 @@ async function onCopy() {
|
|
|
501
645
|
}
|
|
502
646
|
emit("copy", markdown);
|
|
503
647
|
if (settings.clearAfterCopy) {
|
|
504
|
-
|
|
505
|
-
emit("annotations-clear", cleared);
|
|
648
|
+
onClear();
|
|
506
649
|
}
|
|
507
650
|
}
|
|
651
|
+
function dismissUndo() {
|
|
652
|
+
undoFeedback.value = false;
|
|
653
|
+
undoSnapshot.value = [];
|
|
654
|
+
if (undoTimer) {
|
|
655
|
+
clearTimeout(undoTimer);
|
|
656
|
+
undoTimer = null;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
function startUndoTimer() {
|
|
660
|
+
if (undoTimer)
|
|
661
|
+
clearTimeout(undoTimer);
|
|
662
|
+
undoTimer = setTimeout(() => dismissUndo(), UNDO_TIMEOUT_MS);
|
|
663
|
+
}
|
|
664
|
+
function onUndo() {
|
|
665
|
+
const snapshot = undoSnapshot.value;
|
|
666
|
+
dismissUndo();
|
|
667
|
+
if (snapshot.length > 0)
|
|
668
|
+
restoreAnnotations(snapshot);
|
|
669
|
+
}
|
|
508
670
|
function onClear() {
|
|
671
|
+
dismissUndo();
|
|
509
672
|
const cleared = clearAnnotations();
|
|
673
|
+
if (cleared.length === 0)
|
|
674
|
+
return;
|
|
510
675
|
emit("annotations-clear", cleared);
|
|
676
|
+
undoSnapshot.value = cleared;
|
|
677
|
+
undoFeedback.value = true;
|
|
678
|
+
startUndoTimer();
|
|
511
679
|
}
|
|
512
680
|
function onMarkerClick(ann) {
|
|
513
681
|
const scrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
@@ -515,12 +683,16 @@ function onMarkerClick(ann) {
|
|
|
515
683
|
const markerY = ann.isFixed ? ann.y : ann.y - scrollTop;
|
|
516
684
|
editingAnnotation.value = ann;
|
|
517
685
|
pendingPosition.value = { x: markerX, y: markerY };
|
|
518
|
-
pendingElementName.value = getElementName(
|
|
686
|
+
pendingElementName.value = getElementName(
|
|
687
|
+
ann._targetRef?.deref() || document.createElement(ann.element)
|
|
688
|
+
);
|
|
519
689
|
pendingComponentChain.value = ann.vueComponents;
|
|
520
|
-
pendingComputedStyles.value = ann.computedStyles ? Object.fromEntries(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
690
|
+
pendingComputedStyles.value = ann.computedStyles ? Object.fromEntries(
|
|
691
|
+
ann.computedStyles.split("\n").filter(Boolean).map((line) => {
|
|
692
|
+
const idx = line.indexOf(":");
|
|
693
|
+
return idx > -1 ? [line.slice(0, idx).trim(), line.slice(idx + 1).trim()] : [line, ""];
|
|
694
|
+
})
|
|
695
|
+
) : ann._targetRef?.deref() ? getRelevantComputedStyles(ann._targetRef.deref()) : void 0;
|
|
524
696
|
pendingTextSelection.value = null;
|
|
525
697
|
pendingTarget.value = ann._targetRef?.deref() || null;
|
|
526
698
|
transition("input-open");
|
|
@@ -532,7 +704,13 @@ function onInputDelete() {
|
|
|
532
704
|
if (removed)
|
|
533
705
|
emit("annotation-delete", removed);
|
|
534
706
|
resetPendingState();
|
|
535
|
-
|
|
707
|
+
clearHighlight();
|
|
708
|
+
if (peekActive.value) {
|
|
709
|
+
transition("idle");
|
|
710
|
+
peekMode.deactivate();
|
|
711
|
+
} else {
|
|
712
|
+
transition("inspect");
|
|
713
|
+
}
|
|
536
714
|
}
|
|
537
715
|
function onInputSave(comment) {
|
|
538
716
|
if (!editingAnnotation.value)
|
|
@@ -541,7 +719,13 @@ function onInputSave(comment) {
|
|
|
541
719
|
if (updated)
|
|
542
720
|
emit("annotation-update", updated);
|
|
543
721
|
resetPendingState();
|
|
544
|
-
|
|
722
|
+
clearHighlight();
|
|
723
|
+
if (peekActive.value) {
|
|
724
|
+
transition("inspect");
|
|
725
|
+
peekMode.scheduleExit();
|
|
726
|
+
} else {
|
|
727
|
+
transition("inspect");
|
|
728
|
+
}
|
|
545
729
|
}
|
|
546
730
|
function onToggleArea(value) {
|
|
547
731
|
areaSelect.isAreaMode.value = value;
|
|
@@ -596,7 +780,10 @@ onMounted(() => {
|
|
|
596
780
|
document.addEventListener("mousemove", onDocumentMouseMove, true);
|
|
597
781
|
document.addEventListener("mousedown", onDocumentMouseDown, true);
|
|
598
782
|
document.addEventListener("mouseup", onDocumentMouseUp, true);
|
|
599
|
-
document.addEventListener("wheel", onDocumentWheel, {
|
|
783
|
+
document.addEventListener("wheel", onDocumentWheel, {
|
|
784
|
+
passive: true,
|
|
785
|
+
capture: true
|
|
786
|
+
});
|
|
600
787
|
document.addEventListener("click", onDocumentClick, true);
|
|
601
788
|
});
|
|
602
789
|
onBeforeUnmount(() => {
|
|
@@ -613,14 +800,21 @@ onBeforeUnmount(() => {
|
|
|
613
800
|
|
|
614
801
|
<template>
|
|
615
802
|
<component :is="portalWrapper" v-bind="portalProps">
|
|
616
|
-
<div
|
|
803
|
+
<div
|
|
804
|
+
ref="rootEl"
|
|
805
|
+
data-agentation-vue
|
|
806
|
+
:data-va-theme="settings.theme !== 'auto' ? settings.theme : void 0"
|
|
807
|
+
:style="rootStyle"
|
|
808
|
+
>
|
|
617
809
|
<!-- Intercept overlay -->
|
|
618
810
|
<div
|
|
619
811
|
v-if="mode !== 'idle'"
|
|
620
812
|
ref="overlayEl"
|
|
621
813
|
class="__va-intercept"
|
|
622
814
|
:class="{ '__va-intercept--input-open': mode === 'input-open' }"
|
|
623
|
-
:style="
|
|
815
|
+
:style="
|
|
816
|
+
mode === 'inspect' && !effectiveBlockPageInteractions ? { pointerEvents: 'none' } : void 0
|
|
817
|
+
"
|
|
624
818
|
@mousemove="onOverlayMouseMove"
|
|
625
819
|
@mousedown="onOverlayMouseDown"
|
|
626
820
|
@mouseup="onOverlayMouseUp"
|
|
@@ -647,10 +841,11 @@ onBeforeUnmount(() => {
|
|
|
647
841
|
:style="boundingBoxToStyle(areaSelect.areaRect.value)"
|
|
648
842
|
/>
|
|
649
843
|
|
|
650
|
-
<!-- Annotation markers -->
|
|
844
|
+
<!-- Annotation markers (hidden when toolbar is collapsed) -->
|
|
651
845
|
<AnnotationMarker
|
|
652
846
|
v-for="(ann, i) in annotations"
|
|
653
847
|
:key="ann.id"
|
|
848
|
+
:hidden="mode === 'idle'"
|
|
654
849
|
:number="i + 1"
|
|
655
850
|
:x="ann.x"
|
|
656
851
|
:y="ann.y"
|
|
@@ -679,6 +874,7 @@ onBeforeUnmount(() => {
|
|
|
679
874
|
:computed-styles="pendingComputedStyles"
|
|
680
875
|
:initial-comment="editingAnnotation?.comment"
|
|
681
876
|
:is-editing="!!editingAnnotation"
|
|
877
|
+
:mention-candidates="mentionCandidates"
|
|
682
878
|
@add="onInputAdd"
|
|
683
879
|
@cancel="onInputCancel"
|
|
684
880
|
@delete="onInputDelete"
|
|
@@ -698,6 +894,18 @@ onBeforeUnmount(() => {
|
|
|
698
894
|
Copied!
|
|
699
895
|
</div>
|
|
700
896
|
|
|
897
|
+
<!-- Undo clear feedback -->
|
|
898
|
+
<div
|
|
899
|
+
v-if="undoFeedback"
|
|
900
|
+
class="__va-undo-feedback"
|
|
901
|
+
:class="{ '__va-undo-feedback--shifted': copyFeedback }"
|
|
902
|
+
>
|
|
903
|
+
<span>Annotations cleared</span>
|
|
904
|
+
<button type="button" class="__va-undo-btn" @click="onUndo">
|
|
905
|
+
Undo
|
|
906
|
+
</button>
|
|
907
|
+
</div>
|
|
908
|
+
|
|
701
909
|
<!-- Toolbar -->
|
|
702
910
|
<AgentationToolbar
|
|
703
911
|
ref="toolbarRef"
|
|
@@ -707,6 +915,8 @@ onBeforeUnmount(() => {
|
|
|
707
915
|
:is-area-mode="areaSelect.isAreaMode.value"
|
|
708
916
|
:auto-hide-enabled="settings.autoHideToolbar"
|
|
709
917
|
:placement="settings.toolbarPlacement"
|
|
918
|
+
:is-peek-charging="peekMode.isCharging.value"
|
|
919
|
+
:peek-duration-ms="PEEK_HOLD_DURATION_MS"
|
|
710
920
|
@activate="onActivate"
|
|
711
921
|
@deactivate="onDeactivate"
|
|
712
922
|
@copy="onCopy"
|
|
@@ -6,6 +6,8 @@ type __VLS_Props = {
|
|
|
6
6
|
isAreaMode: boolean;
|
|
7
7
|
autoHideEnabled: boolean;
|
|
8
8
|
placement: ToolbarAnchor;
|
|
9
|
+
isPeekCharging: boolean;
|
|
10
|
+
peekDurationMs: number;
|
|
9
11
|
};
|
|
10
12
|
declare const _default: import("vue-demi").DefineComponent<__VLS_Props, {
|
|
11
13
|
expanded: import("vue-demi").Ref<boolean, boolean>;
|
|
@@ -10,7 +10,9 @@ const props = defineProps({
|
|
|
10
10
|
isPaused: { type: Boolean, required: true },
|
|
11
11
|
isAreaMode: { type: Boolean, required: true },
|
|
12
12
|
autoHideEnabled: { type: Boolean, required: true },
|
|
13
|
-
placement: { type: String, required: true }
|
|
13
|
+
placement: { type: String, required: true },
|
|
14
|
+
isPeekCharging: { type: Boolean, required: true },
|
|
15
|
+
peekDurationMs: { type: Number, required: true }
|
|
14
16
|
});
|
|
15
17
|
const emit = defineEmits(["activate", "deactivate", "copy", "clear", "toggle-pause", "toggle-area", "update:placement", "open-settings", "drag-start", "drag-end"]);
|
|
16
18
|
const expanded = ref(false);
|
|
@@ -77,8 +79,7 @@ function onDeactivate() {
|
|
|
77
79
|
emit("deactivate");
|
|
78
80
|
}
|
|
79
81
|
function onClear() {
|
|
80
|
-
|
|
81
|
-
emit("clear");
|
|
82
|
+
emit("clear");
|
|
82
83
|
}
|
|
83
84
|
function onOpenSettings(e) {
|
|
84
85
|
const anchorEl = e.currentTarget instanceof HTMLElement ? e.currentTarget : null;
|
|
@@ -134,6 +135,14 @@ defineExpose({ expanded, placement });
|
|
|
134
135
|
>
|
|
135
136
|
<VaIcon name="cursor" />
|
|
136
137
|
<span v-if="annotationCount > 0" class="__va-toolbar-badge">{{ annotationCount }}</span>
|
|
138
|
+
<svg
|
|
139
|
+
v-if="isPeekCharging"
|
|
140
|
+
class="__va-peek-ring"
|
|
141
|
+
viewBox="0 0 46 46"
|
|
142
|
+
aria-hidden="true"
|
|
143
|
+
>
|
|
144
|
+
<circle cx="23" cy="23" r="21" :style="{ animationDuration: `${peekDurationMs}ms` }" />
|
|
145
|
+
</svg>
|
|
137
146
|
</button>
|
|
138
147
|
</div>
|
|
139
148
|
|
|
@@ -6,6 +6,8 @@ type __VLS_Props = {
|
|
|
6
6
|
isAreaMode: boolean;
|
|
7
7
|
autoHideEnabled: boolean;
|
|
8
8
|
placement: ToolbarAnchor;
|
|
9
|
+
isPeekCharging: boolean;
|
|
10
|
+
peekDurationMs: number;
|
|
9
11
|
};
|
|
10
12
|
declare const _default: import("vue-demi").DefineComponent<__VLS_Props, {
|
|
11
13
|
expanded: import("vue-demi").Ref<boolean, boolean>;
|