agentation-vue 0.2.6 → 0.2.10

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.
@@ -1,5 +1,13 @@
1
1
  <script setup>
2
- import { isVue2 as _isVue2, computed, defineComponent, onBeforeUnmount, onMounted, ref, watch } from "vue-demi";
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,7 +18,10 @@ 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 { DEFAULT_SHORTCUT_CONFIG, useKeyboardShortcuts } from "./composables/useKeyboardShortcuts.mjs";
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";
@@ -18,7 +29,15 @@ import { useSettings } from "./composables/useSettings.mjs";
18
29
  import { useTextSelection } from "./composables/useTextSelection.mjs";
19
30
  import { isInsideAgentationTree } from "./utils/agentation-tree.mjs";
20
31
  import { copyToClipboard } from "./utils/clipboard.mjs";
21
- import { isFixed as checkIsFixed, detectVueComponents, getAccessibilityInfo, getComputedStylesSummary, getNearbyElements, getNearbyText, getRelevantComputedStyles } from "./utils/dom-inspector.mjs";
32
+ import {
33
+ isFixed as checkIsFixed,
34
+ detectVueComponents,
35
+ getAccessibilityInfo,
36
+ getComputedStylesSummary,
37
+ getNearbyElements,
38
+ getNearbyText,
39
+ getRelevantComputedStyles
40
+ } from "./utils/dom-inspector.mjs";
22
41
  import { createPortalContainer, destroyPortalContainer } from "./utils/portal.mjs";
23
42
  import { getElementName, getElementPath } from "./utils/selectors.mjs";
24
43
  import { boundingBoxToStyle } from "./utils/style.mjs";
@@ -60,8 +79,24 @@ const toolbarRef = ref(null);
60
79
  const currentUrl = ref(props.pageUrl || getCurrentUrl());
61
80
  const { settings } = useSettings();
62
81
  const { mode, transition } = useInteractionMode();
63
- const { annotations, addAnnotation, removeAnnotation, updateAnnotation, clearAnnotations, setScopeUrl } = useAnnotations(currentUrl.value);
64
- const { hoveredRect, hoveredName, hoveredComponentChain, onMouseMove, clearHighlight, getElementUnderOverlay, cleanup: cleanupDetection } = useElementDetection(overlayEl, () => settings.showComponentTree);
82
+ const {
83
+ annotations,
84
+ addAnnotation,
85
+ removeAnnotation,
86
+ updateAnnotation,
87
+ clearAnnotations,
88
+ restoreAnnotations,
89
+ setScopeUrl
90
+ } = useAnnotations(currentUrl.value);
91
+ const {
92
+ hoveredRect,
93
+ hoveredName,
94
+ hoveredComponentChain,
95
+ onMouseMove,
96
+ clearHighlight,
97
+ getElementUnderOverlay,
98
+ cleanup: cleanupDetection
99
+ } = useElementDetection(overlayEl, () => settings.showComponentTree);
65
100
  const textSelection = useTextSelection(mode);
66
101
  const multiSelect = useMultiSelect(mode, transition);
67
102
  const areaSelect = useAreaSelect(mode, transition);
@@ -78,11 +113,17 @@ const editingAnnotation = ref(null);
78
113
  const settingsOpen = ref(false);
79
114
  const settingsAnchorEl = ref(null);
80
115
  const copyFeedback = ref(false);
116
+ const undoFeedback = ref(false);
117
+ const undoSnapshot = ref([]);
118
+ let undoTimer = null;
119
+ const UNDO_TIMEOUT_MS = 5e3;
81
120
  const toolbarDragging = ref(false);
82
121
  const DRAG_END_SUPPRESSION_MS = 500;
83
122
  const SETTINGS_CLOSE_SUPPRESSION_MS = 220;
84
123
  let suppressInteractionsUntil = 0;
85
- const effectiveBlockPageInteractions = computed(() => props.blockPageInteractions ?? settings.blockPageInteractions);
124
+ const effectiveBlockPageInteractions = computed(
125
+ () => props.blockPageInteractions ?? settings.blockPageInteractions
126
+ );
86
127
  const rootStyle = computed(() => {
87
128
  const hex = settings.markerColor;
88
129
  if (!hex)
@@ -106,7 +147,16 @@ const pendingMarkerY = computed(() => {
106
147
  return 0;
107
148
  return pendingPosition.value.y + (window.scrollY || document.documentElement.scrollTop);
108
149
  });
109
- const pendingIsSelection = computed(() => mode.value === "input-open" && !editingAnnotation.value && (multiSelect.selectedElements.value.length > 0 || !!areaSelect.areaRect.value));
150
+ const pendingIsSelection = computed(
151
+ () => mode.value === "input-open" && !editingAnnotation.value && (multiSelect.selectedElements.value.length > 0 || !!areaSelect.areaRect.value)
152
+ );
153
+ const mentionCandidates = computed(
154
+ () => annotations.value.map((ann, i) => ({
155
+ id: ann.id,
156
+ displayNumber: i + 1,
157
+ commentPreview: ann.comment.replace(/@\[\d+\]/g, "@\u2026").slice(0, 40) + (ann.comment.length > 40 ? "\u2026" : "")
158
+ })).filter((c) => !editingAnnotation.value || c.id !== editingAnnotation.value.id)
159
+ );
110
160
  let portalContainer = null;
111
161
  const isVue2 = _isVue2;
112
162
  const PassThrough = defineComponent({
@@ -115,8 +165,12 @@ const PassThrough = defineComponent({
115
165
  return (typeof slot === "function" ? slot() : slot?.[0]) || null;
116
166
  }
117
167
  });
118
- const portalWrapper = computed(() => props.disablePortal || isVue2 ? PassThrough : "Teleport");
119
- const portalProps = computed(() => props.disablePortal || isVue2 ? {} : { to: "body" });
168
+ const portalWrapper = computed(
169
+ () => props.disablePortal || isVue2 ? PassThrough : "Teleport"
170
+ );
171
+ const portalProps = computed(
172
+ () => props.disablePortal || isVue2 ? {} : { to: "body" }
173
+ );
120
174
  onMounted(() => {
121
175
  if (!props.disablePortal && isVue2 && rootEl.value) {
122
176
  portalContainer = createPortalContainer();
@@ -124,39 +178,68 @@ onMounted(() => {
124
178
  }
125
179
  });
126
180
  onBeforeUnmount(() => {
181
+ dismissUndo();
127
182
  animPause.cleanup();
128
183
  cleanupDetection();
129
184
  if (portalContainer) {
130
185
  destroyPortalContainer(portalContainer);
131
186
  }
132
187
  });
133
- watch(() => props.outputDetail, (v) => {
134
- if (v)
135
- settings.outputDetail = v;
136
- }, { immediate: true });
137
- watch(() => props.markerColor, (v) => {
138
- if (v)
139
- settings.markerColor = v;
140
- }, { immediate: true });
141
- watch(() => props.theme, (v) => {
142
- if (v)
143
- settings.theme = v;
144
- }, { immediate: true });
145
- watch(() => props.blockPageInteractions, (v) => {
146
- if (v)
147
- settings.blockPageInteractions = v;
148
- }, { immediate: true });
149
- watch(() => props.autoHideToolbar, (v) => {
150
- if (v)
151
- settings.autoHideToolbar = v;
152
- }, { immediate: true });
153
- watch(() => props.pageUrl, (url) => {
154
- syncUrlScope(url || getCurrentUrl());
155
- }, { immediate: true });
156
- watch(() => props.activationKey, (v) => {
157
- if (v !== void 0)
158
- settings.activationKey = v;
159
- }, { immediate: true });
188
+ watch(
189
+ () => props.outputDetail,
190
+ (v) => {
191
+ if (v)
192
+ settings.outputDetail = v;
193
+ },
194
+ { immediate: true }
195
+ );
196
+ watch(
197
+ () => props.markerColor,
198
+ (v) => {
199
+ if (v)
200
+ settings.markerColor = v;
201
+ },
202
+ { immediate: true }
203
+ );
204
+ watch(
205
+ () => props.theme,
206
+ (v) => {
207
+ if (v)
208
+ settings.theme = v;
209
+ },
210
+ { immediate: true }
211
+ );
212
+ watch(
213
+ () => props.blockPageInteractions,
214
+ (v) => {
215
+ if (v)
216
+ settings.blockPageInteractions = v;
217
+ },
218
+ { immediate: true }
219
+ );
220
+ watch(
221
+ () => props.autoHideToolbar,
222
+ (v) => {
223
+ if (v)
224
+ settings.autoHideToolbar = v;
225
+ },
226
+ { immediate: true }
227
+ );
228
+ watch(
229
+ () => props.pageUrl,
230
+ (url) => {
231
+ syncUrlScope(url || getCurrentUrl());
232
+ },
233
+ { immediate: true }
234
+ );
235
+ watch(
236
+ () => props.activationKey,
237
+ (v) => {
238
+ if (v !== void 0)
239
+ settings.activationKey = v;
240
+ },
241
+ { immediate: true }
242
+ );
160
243
  function onActivate() {
161
244
  transition("inspect");
162
245
  }
@@ -207,7 +290,10 @@ function onOverlayMouseUp(e) {
207
290
  areaSelect.onMouseUp();
208
291
  const rect = areaSelect.areaRect.value;
209
292
  if (rect && rect.width > 10 && rect.height > 10) {
210
- pendingPosition.value = { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
293
+ pendingPosition.value = {
294
+ x: rect.x + rect.width / 2,
295
+ y: rect.y + rect.height / 2
296
+ };
211
297
  pendingElementName.value = "Area selection";
212
298
  pendingComponentChain.value = void 0;
213
299
  pendingComputedStyles.value = void 0;
@@ -229,7 +315,9 @@ function onOverlayMouseUp(e) {
229
315
  };
230
316
  pendingElementName.value = `"${textResult.selectedText.slice(0, 30)}"`;
231
317
  pendingComponentChain.value = getVueComponents(textResult.anchorElement);
232
- pendingComputedStyles.value = getRelevantComputedStyles(textResult.anchorElement);
318
+ pendingComputedStyles.value = getRelevantComputedStyles(
319
+ textResult.anchorElement
320
+ );
233
321
  pendingTarget.value = textResult.anchorElement;
234
322
  pendingTextSelection.value = {
235
323
  text: textResult.selectedText,
@@ -282,7 +370,10 @@ function shouldUseDocumentFallbackEvents() {
282
370
  return mode.value === "inspect" && !effectiveBlockPageInteractions.value && !isInteractionLocked();
283
371
  }
284
372
  function lockInteractionsTemporarily(durationMs) {
285
- suppressInteractionsUntil = Math.max(suppressInteractionsUntil, Date.now() + durationMs);
373
+ suppressInteractionsUntil = Math.max(
374
+ suppressInteractionsUntil,
375
+ Date.now() + durationMs
376
+ );
286
377
  }
287
378
  function isInteractionLocked() {
288
379
  return settingsOpen.value || toolbarDragging.value || Date.now() < suppressInteractionsUntil;
@@ -297,6 +388,7 @@ function closeSettings(lockInteractions = true) {
297
388
  function syncUrlScope(nextUrl) {
298
389
  if (!nextUrl || currentUrl.value === nextUrl)
299
390
  return;
391
+ dismissUndo();
300
392
  currentUrl.value = nextUrl;
301
393
  setScopeUrl(nextUrl);
302
394
  }
@@ -395,7 +487,9 @@ function onInputAdd(comment) {
395
487
  comment,
396
488
  url,
397
489
  element: "multi",
398
- elementPath: `region at (${Math.round(boundingBox.x)}, ${Math.round(boundingBox.y)})`,
490
+ elementPath: `region at (${Math.round(boundingBox.x)}, ${Math.round(
491
+ boundingBox.y
492
+ )})`,
399
493
  isMultiSelect: true,
400
494
  elements,
401
495
  boundingBox,
@@ -471,7 +565,12 @@ function onInputAdd(comment) {
471
565
  vueComponents: getVueComponents(el),
472
566
  nearbyElements: getNearbyElements(el),
473
567
  nearbyText: getNearbyText(el),
474
- boundingBox: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
568
+ boundingBox: {
569
+ x: rect.x,
570
+ y: rect.y,
571
+ width: rect.width,
572
+ height: rect.height
573
+ },
475
574
  cssClasses: detail === "forensic" ? Array.from(el.classList).join(" ") : void 0,
476
575
  fullPath: detail === "forensic" ? getElementPath(el) : void 0,
477
576
  computedStyles: detail === "forensic" ? getComputedStylesSummary(el) : void 0,
@@ -489,7 +588,11 @@ function onInputCancel() {
489
588
  transition("inspect");
490
589
  }
491
590
  async function onCopy() {
492
- const markdown = formatAnnotations(annotations.value, settings.outputDetail, resolvedUrl.value);
591
+ const markdown = formatAnnotations(
592
+ annotations.value,
593
+ settings.outputDetail,
594
+ resolvedUrl.value
595
+ );
493
596
  if (props.copyToClipboard !== false) {
494
597
  const success = await copyToClipboard(markdown);
495
598
  if (!success)
@@ -501,13 +604,37 @@ async function onCopy() {
501
604
  }
502
605
  emit("copy", markdown);
503
606
  if (settings.clearAfterCopy) {
504
- const cleared = clearAnnotations();
505
- emit("annotations-clear", cleared);
607
+ onClear();
608
+ }
609
+ }
610
+ function dismissUndo() {
611
+ undoFeedback.value = false;
612
+ undoSnapshot.value = [];
613
+ if (undoTimer) {
614
+ clearTimeout(undoTimer);
615
+ undoTimer = null;
506
616
  }
507
617
  }
618
+ function startUndoTimer() {
619
+ if (undoTimer)
620
+ clearTimeout(undoTimer);
621
+ undoTimer = setTimeout(() => dismissUndo(), UNDO_TIMEOUT_MS);
622
+ }
623
+ function onUndo() {
624
+ const snapshot = undoSnapshot.value;
625
+ dismissUndo();
626
+ if (snapshot.length > 0)
627
+ restoreAnnotations(snapshot);
628
+ }
508
629
  function onClear() {
630
+ dismissUndo();
509
631
  const cleared = clearAnnotations();
632
+ if (cleared.length === 0)
633
+ return;
510
634
  emit("annotations-clear", cleared);
635
+ undoSnapshot.value = cleared;
636
+ undoFeedback.value = true;
637
+ startUndoTimer();
511
638
  }
512
639
  function onMarkerClick(ann) {
513
640
  const scrollTop = window.scrollY || document.documentElement.scrollTop;
@@ -515,12 +642,16 @@ function onMarkerClick(ann) {
515
642
  const markerY = ann.isFixed ? ann.y : ann.y - scrollTop;
516
643
  editingAnnotation.value = ann;
517
644
  pendingPosition.value = { x: markerX, y: markerY };
518
- pendingElementName.value = getElementName(ann._targetRef?.deref() || document.createElement(ann.element));
645
+ pendingElementName.value = getElementName(
646
+ ann._targetRef?.deref() || document.createElement(ann.element)
647
+ );
519
648
  pendingComponentChain.value = ann.vueComponents;
520
- pendingComputedStyles.value = ann.computedStyles ? Object.fromEntries(ann.computedStyles.split("\n").filter(Boolean).map((line) => {
521
- const idx = line.indexOf(":");
522
- return idx > -1 ? [line.slice(0, idx).trim(), line.slice(idx + 1).trim()] : [line, ""];
523
- })) : ann._targetRef?.deref() ? getRelevantComputedStyles(ann._targetRef.deref()) : void 0;
649
+ pendingComputedStyles.value = ann.computedStyles ? Object.fromEntries(
650
+ ann.computedStyles.split("\n").filter(Boolean).map((line) => {
651
+ const idx = line.indexOf(":");
652
+ return idx > -1 ? [line.slice(0, idx).trim(), line.slice(idx + 1).trim()] : [line, ""];
653
+ })
654
+ ) : ann._targetRef?.deref() ? getRelevantComputedStyles(ann._targetRef.deref()) : void 0;
524
655
  pendingTextSelection.value = null;
525
656
  pendingTarget.value = ann._targetRef?.deref() || null;
526
657
  transition("input-open");
@@ -596,7 +727,10 @@ onMounted(() => {
596
727
  document.addEventListener("mousemove", onDocumentMouseMove, true);
597
728
  document.addEventListener("mousedown", onDocumentMouseDown, true);
598
729
  document.addEventListener("mouseup", onDocumentMouseUp, true);
599
- document.addEventListener("wheel", onDocumentWheel, { passive: true, capture: true });
730
+ document.addEventListener("wheel", onDocumentWheel, {
731
+ passive: true,
732
+ capture: true
733
+ });
600
734
  document.addEventListener("click", onDocumentClick, true);
601
735
  });
602
736
  onBeforeUnmount(() => {
@@ -613,14 +747,21 @@ onBeforeUnmount(() => {
613
747
 
614
748
  <template>
615
749
  <component :is="portalWrapper" v-bind="portalProps">
616
- <div ref="rootEl" data-agentation-vue :data-va-theme="settings.theme !== 'auto' ? settings.theme : void 0" :style="rootStyle">
750
+ <div
751
+ ref="rootEl"
752
+ data-agentation-vue
753
+ :data-va-theme="settings.theme !== 'auto' ? settings.theme : void 0"
754
+ :style="rootStyle"
755
+ >
617
756
  <!-- Intercept overlay -->
618
757
  <div
619
758
  v-if="mode !== 'idle'"
620
759
  ref="overlayEl"
621
760
  class="__va-intercept"
622
761
  :class="{ '__va-intercept--input-open': mode === 'input-open' }"
623
- :style="mode === 'inspect' && !effectiveBlockPageInteractions ? { pointerEvents: 'none' } : void 0"
762
+ :style="
763
+ mode === 'inspect' && !effectiveBlockPageInteractions ? { pointerEvents: 'none' } : void 0
764
+ "
624
765
  @mousemove="onOverlayMouseMove"
625
766
  @mousedown="onOverlayMouseDown"
626
767
  @mouseup="onOverlayMouseUp"
@@ -679,6 +820,7 @@ onBeforeUnmount(() => {
679
820
  :computed-styles="pendingComputedStyles"
680
821
  :initial-comment="editingAnnotation?.comment"
681
822
  :is-editing="!!editingAnnotation"
823
+ :mention-candidates="mentionCandidates"
682
824
  @add="onInputAdd"
683
825
  @cancel="onInputCancel"
684
826
  @delete="onInputDelete"
@@ -698,6 +840,18 @@ onBeforeUnmount(() => {
698
840
  Copied!
699
841
  </div>
700
842
 
843
+ <!-- Undo clear feedback -->
844
+ <div
845
+ v-if="undoFeedback"
846
+ class="__va-undo-feedback"
847
+ :class="{ '__va-undo-feedback--shifted': copyFeedback }"
848
+ >
849
+ <span>Annotations cleared</span>
850
+ <button type="button" class="__va-undo-btn" @click="onUndo">
851
+ Undo
852
+ </button>
853
+ </div>
854
+
701
855
  <!-- Toolbar -->
702
856
  <AgentationToolbar
703
857
  ref="toolbarRef"
@@ -77,8 +77,7 @@ function onDeactivate() {
77
77
  emit("deactivate");
78
78
  }
79
79
  function onClear() {
80
- if (confirm("Clear all annotations?"))
81
- emit("clear");
80
+ emit("clear");
82
81
  }
83
82
  function onOpenSettings(e) {
84
83
  const anchorEl = e.currentTarget instanceof HTMLElement ? e.currentTarget : null;
@@ -1,3 +1,4 @@
1
+ import type { MentionCandidate } from '../utils/mention';
1
2
  type __VLS_Props = {
2
3
  position: {
3
4
  x: number;
@@ -8,6 +9,7 @@ type __VLS_Props = {
8
9
  computedStyles?: Record<string, string>;
9
10
  initialComment?: string;
10
11
  isEditing?: boolean;
12
+ mentionCandidates?: MentionCandidate[];
11
13
  };
12
14
  declare const _default: import("vue-demi").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue-demi").ComponentOptionsMixin, import("vue-demi").ComponentOptionsMixin, {
13
15
  cancel: () => any;
@@ -1,6 +1,9 @@
1
1
  <script setup>
2
- import { computed, onMounted, ref } from "vue-demi";
2
+ import { computed, onMounted, ref, toRef } from "vue-demi";
3
+ import { useMentionDropdown } from "../composables/useMentionDropdown.mjs";
4
+ import { hydrateMentions, serializeMentions } from "../utils/mention.mjs";
3
5
  import ComponentChain from "./ComponentChain.vue";
6
+ import MentionDropdown from "./MentionDropdown.vue";
4
7
  import VaButton from "./VaButton.vue";
5
8
  import VaIcon from "./VaIcon.vue";
6
9
  const props = defineProps({
@@ -9,12 +12,16 @@ const props = defineProps({
9
12
  componentChain: { type: String, required: false },
10
13
  computedStyles: { type: Object, required: false },
11
14
  initialComment: { type: String, required: false },
12
- isEditing: { type: Boolean, required: false }
15
+ isEditing: { type: Boolean, required: false },
16
+ mentionCandidates: { type: Array, required: false }
13
17
  });
14
18
  const emit = defineEmits(["add", "cancel", "delete"]);
15
- const comment = ref(props.initialComment || "");
16
19
  const inputEl = ref(null);
20
+ const commentText = ref(props.initialComment || "");
17
21
  const computedStyleEntries = computed(() => Object.entries(props.computedStyles || {}));
22
+ const candidates = toRef(props, "mentionCandidates");
23
+ const safeCandidates = computed(() => candidates.value || []);
24
+ const mention = useMentionDropdown(inputEl, safeCandidates);
18
25
  const inputStyle = computed(() => {
19
26
  const x = Math.min(props.position.x, window.innerWidth - 380);
20
27
  const y = Math.min(props.position.y + 20, window.innerHeight - 150);
@@ -23,14 +30,71 @@ const inputStyle = computed(() => {
23
30
  top: `${Math.max(10, y)}px`
24
31
  };
25
32
  });
33
+ function autoResize() {
34
+ const el = inputEl.value;
35
+ if (!el)
36
+ return;
37
+ el.style.height = "auto";
38
+ el.style.height = `${el.scrollHeight}px`;
39
+ }
40
+ function getComment() {
41
+ const el = inputEl.value;
42
+ if (!el)
43
+ return "";
44
+ return serializeMentions(el);
45
+ }
46
+ function onInput() {
47
+ commentText.value = getComment();
48
+ autoResize();
49
+ mention.checkForTrigger();
50
+ }
51
+ function onKeyDown(e) {
52
+ if (mention.onKeyDown(e))
53
+ return;
54
+ if (e.key === "Enter" && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) {
55
+ e.preventDefault();
56
+ onAdd();
57
+ } else if (e.key === "Escape") {
58
+ if (mention.isOpen.value) {
59
+ e.preventDefault();
60
+ mention.close();
61
+ } else {
62
+ emit("cancel");
63
+ }
64
+ }
65
+ }
26
66
  function onAdd() {
27
- const text = comment.value.trim();
67
+ const text = getComment().trim();
28
68
  if (!text)
29
69
  return;
30
70
  emit("add", text);
31
71
  }
72
+ function onPaste(e) {
73
+ e.preventDefault();
74
+ const text = e.clipboardData?.getData("text/plain") || "";
75
+ document.execCommand("insertText", false, text);
76
+ }
77
+ function onSelectCandidate(candidate) {
78
+ mention.selectCandidate(candidate);
79
+ }
32
80
  onMounted(() => {
33
- inputEl.value?.focus();
81
+ const el = inputEl.value;
82
+ if (!el)
83
+ return;
84
+ if (props.initialComment) {
85
+ const html = hydrateMentions(props.initialComment, safeCandidates.value);
86
+ el.innerHTML = html;
87
+ }
88
+ el.focus();
89
+ const sel = window.getSelection();
90
+ if (sel && el.childNodes.length > 0) {
91
+ const range = document.createRange();
92
+ range.selectNodeContents(el);
93
+ range.collapse(false);
94
+ sel.removeAllRanges();
95
+ sel.addRange(range);
96
+ }
97
+ autoResize();
34
98
  });
35
99
  </script>
36
100
 
@@ -66,13 +130,24 @@ onMounted(() => {
66
130
  <ComponentChain :chain="componentChain" variant="light" truncate="leaf" />
67
131
  </div>
68
132
  <span v-else class="__va-input-label">{{ elementName || "Annotation" }}</span>
69
- <input
133
+ <div
70
134
  ref="inputEl"
71
- v-model="comment"
72
- placeholder="Add a comment..."
73
- @keydown.enter="onAdd"
74
- @keydown.escape="$emit('cancel')"
75
- >
135
+ class="__va-input-editable"
136
+ contenteditable="true"
137
+ role="textbox"
138
+ aria-multiline="true"
139
+ data-placeholder="Add a comment..."
140
+ @input="onInput"
141
+ @keydown="onKeyDown"
142
+ @paste="onPaste"
143
+ />
144
+ <MentionDropdown
145
+ :open="mention.isOpen.value"
146
+ :candidates="mention.filteredCandidates.value"
147
+ :active-index="mention.activeIndex.value"
148
+ :position="mention.dropdownPosition.value"
149
+ @select="onSelectCandidate"
150
+ />
76
151
  <div class="__va-input-actions">
77
152
  <button
78
153
  v-if="isEditing"
@@ -86,7 +161,7 @@ onMounted(() => {
86
161
  <VaButton variant="secondary" @click="$emit('cancel')">
87
162
  Cancel
88
163
  </VaButton>
89
- <VaButton :disabled="!comment.trim()" @click="onAdd">
164
+ <VaButton :disabled="!commentText.trim()" @click="onAdd">
90
165
  {{ isEditing ? "Save" : "Add" }}
91
166
  </VaButton>
92
167
  </div>
@@ -1,3 +1,4 @@
1
+ import type { MentionCandidate } from '../utils/mention';
1
2
  type __VLS_Props = {
2
3
  position: {
3
4
  x: number;
@@ -8,6 +9,7 @@ type __VLS_Props = {
8
9
  computedStyles?: Record<string, string>;
9
10
  initialComment?: string;
10
11
  isEditing?: boolean;
12
+ mentionCandidates?: MentionCandidate[];
11
13
  };
12
14
  declare const _default: import("vue-demi").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue-demi").ComponentOptionsMixin, import("vue-demi").ComponentOptionsMixin, {
13
15
  cancel: () => any;
@@ -0,0 +1,16 @@
1
+ import type { MentionCandidate } from '../utils/mention';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ candidates: MentionCandidate[];
5
+ activeIndex: number;
6
+ position: {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ };
11
+ declare const _default: import("vue-demi").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue-demi").ComponentOptionsMixin, import("vue-demi").ComponentOptionsMixin, {
12
+ select: (candidate: MentionCandidate) => any;
13
+ }, string, import("vue-demi").PublicProps, Readonly<__VLS_Props> & Readonly<{
14
+ onSelect?: ((candidate: MentionCandidate) => any) | undefined;
15
+ }>, {}, {}, {}, {}, string, import("vue-demi").ComponentProvideOptions, false, {}, any>;
16
+ export default _default;
@@ -0,0 +1,41 @@
1
+ <script setup>
2
+ import { computed } from "vue-demi";
3
+ const props = defineProps({
4
+ open: { type: Boolean, required: true },
5
+ candidates: { type: Array, required: true },
6
+ activeIndex: { type: Number, required: true },
7
+ position: { type: Object, required: true }
8
+ });
9
+ defineEmits(["select"]);
10
+ const dropdownStyle = computed(() => {
11
+ if (!props.open)
12
+ return { display: "none" };
13
+ return {
14
+ left: `${props.position.x}px`,
15
+ top: `${props.position.y}px`
16
+ };
17
+ });
18
+ </script>
19
+
20
+ <template>
21
+ <div
22
+ v-show="open && candidates.length > 0"
23
+ class="__va-mention-dropdown"
24
+ :style="dropdownStyle"
25
+ role="listbox"
26
+ data-agentation-vue
27
+ >
28
+ <div
29
+ v-for="(candidate, i) in candidates"
30
+ :key="candidate.id"
31
+ class="__va-mention-option"
32
+ :class="{ '__va-mention-option--active': i === activeIndex }"
33
+ role="option"
34
+ :aria-selected="i === activeIndex"
35
+ @mousedown.prevent="$emit('select', candidate)"
36
+ >
37
+ <span class="__va-mention-option-number">{{ candidate.displayNumber }}</span>
38
+ <span class="__va-mention-option-preview">{{ candidate.commentPreview }}</span>
39
+ </div>
40
+ </div>
41
+ </template>
@@ -0,0 +1,16 @@
1
+ import type { MentionCandidate } from '../utils/mention';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ candidates: MentionCandidate[];
5
+ activeIndex: number;
6
+ position: {
7
+ x: number;
8
+ y: number;
9
+ };
10
+ };
11
+ declare const _default: import("vue-demi").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue-demi").ComponentOptionsMixin, import("vue-demi").ComponentOptionsMixin, {
12
+ select: (candidate: MentionCandidate) => any;
13
+ }, string, import("vue-demi").PublicProps, Readonly<__VLS_Props> & Readonly<{
14
+ onSelect?: ((candidate: MentionCandidate) => any) | undefined;
15
+ }>, {}, {}, {}, {}, string, import("vue-demi").ComponentProvideOptions, false, {}, any>;
16
+ export default _default;
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { computed, toRef } from "vue-demi";
3
+ import { VA_VERSION } from "../constants.mjs";
3
4
  import { vaTooltipDirective } from "../directives/vaTooltip.mjs";
4
5
  import VaIcon from "./VaIcon.vue";
5
6
  import VaToggle from "./VaToggle.vue";
@@ -36,6 +37,7 @@ function toggleTheme() {
36
37
  <template>
37
38
  <div class="__va-settings" data-agentation-vue @click.stop>
38
39
  <div class="__va-settings-top">
40
+ <span class="__va-settings-title">Agentation vue <span class="__va-settings-version">v{{ VA_VERSION }}</span></span>
39
41
  <button v-va-tooltip="'Toggle theme'" type="button" class="__va-theme-toggle" @click="toggleTheme">
40
42
  <VaIcon :name="themeIcon" />
41
43
  </button>
@@ -138,12 +138,10 @@ function schedulePositionUpdate() {
138
138
  function onDocumentPointerDown(e) {
139
139
  if (!props.open)
140
140
  return;
141
- const target = e.target;
142
- if (!target)
141
+ const path = e.composedPath();
142
+ if (panelEl.value && path.includes(panelEl.value))
143
143
  return;
144
- if (panelEl.value?.contains(target))
145
- return;
146
- if (props.anchorEl?.contains(target))
144
+ if (props.anchorEl && path.includes(props.anchorEl))
147
145
  return;
148
146
  emit("close");
149
147
  }
@@ -4,6 +4,7 @@ declare function addAnnotation(annotation: Omit<Annotation, 'id' | 'timestamp'>)
4
4
  declare function removeAnnotation(id: string): Annotation | undefined;
5
5
  declare function updateAnnotation(id: string, updates: Partial<Annotation>): Annotation | undefined;
6
6
  declare function clearAnnotations(): Annotation[];
7
+ declare function restoreAnnotations(items: Annotation[]): void;
7
8
  export declare function setAnnotationStorage(adapter: StorageAdapter): void;
8
9
  export declare function resetAnnotationStorage(): void;
9
10
  export declare function useAnnotations(initialUrl?: string): {
@@ -102,6 +103,7 @@ export declare function useAnnotations(initialUrl?: string): {
102
103
  removeAnnotation: typeof removeAnnotation;
103
104
  updateAnnotation: typeof updateAnnotation;
104
105
  clearAnnotations: typeof clearAnnotations;
106
+ restoreAnnotations: typeof restoreAnnotations;
105
107
  setScopeUrl: typeof setScopeUrl;
106
108
  };
107
109
  export {};
@@ -110,6 +110,11 @@ function clearAnnotations() {
110
110
  save();
111
111
  return cleared;
112
112
  }
113
+ function restoreAnnotations(items) {
114
+ annotations.value.push(...items);
115
+ counter = getCounterSeed(annotations.value);
116
+ save();
117
+ }
113
118
  setScopeUrl(scopedUrl);
114
119
  export function setAnnotationStorage(adapter) {
115
120
  annotationStorage = adapter;
@@ -121,5 +126,5 @@ export function resetAnnotationStorage() {
121
126
  }
122
127
  export function useAnnotations(initialUrl = getCurrentUrl()) {
123
128
  setScopeUrl(initialUrl);
124
- return { annotations, addAnnotation, removeAnnotation, updateAnnotation, clearAnnotations, setScopeUrl };
129
+ return { annotations, addAnnotation, removeAnnotation, updateAnnotation, clearAnnotations, restoreAnnotations, setScopeUrl };
125
130
  }
@@ -50,6 +50,7 @@ export function useKeyboardShortcuts(options) {
50
50
  let lastActivationKeyUpTime = 0;
51
51
  let listenerAttached = false;
52
52
  let mergedKeymap = { ...DEFAULT_KEYMAP, ...options.config.value.keymap };
53
+ const suppressedKeyUps = /* @__PURE__ */ new Set();
53
54
  watch(options.config, (cfg2) => {
54
55
  mergedKeymap = { ...DEFAULT_KEYMAP, ...cfg2.keymap };
55
56
  });
@@ -129,9 +130,15 @@ export function useKeyboardShortcuts(options) {
129
130
  break;
130
131
  }
131
132
  }
132
- function consume(e) {
133
+ function normalizeKey(key) {
134
+ return key.length === 1 ? key.toLowerCase() : key;
135
+ }
136
+ function consume(e, suppressKeyUp = false) {
133
137
  e.preventDefault();
138
+ e.stopImmediatePropagation();
134
139
  e.stopPropagation();
140
+ if (suppressKeyUp)
141
+ suppressedKeyUps.add(normalizeKey(e.key));
135
142
  }
136
143
  function onKeyDown(e) {
137
144
  if (e.repeat)
@@ -145,14 +152,14 @@ export function useKeyboardShortcuts(options) {
145
152
  }
146
153
  if (scope === "settings") {
147
154
  if (e.key === "Escape") {
148
- consume(e);
155
+ consume(e, true);
149
156
  actions.closeSettings();
150
157
  }
151
158
  return;
152
159
  }
153
160
  if (scope === "input") {
154
161
  if (e.key === "Escape") {
155
- consume(e);
162
+ consume(e, true);
156
163
  actions.inputCancel();
157
164
  }
158
165
  return;
@@ -167,11 +174,17 @@ export function useKeyboardShortcuts(options) {
167
174
  if (!action)
168
175
  return;
169
176
  if (cfg().priorityWhenOpen) {
170
- consume(e);
177
+ consume(e, true);
171
178
  }
172
179
  executeAction(action);
173
180
  }
174
181
  function onKeyUp(e) {
182
+ const normalizedKey = normalizeKey(e.key);
183
+ if (suppressedKeyUps.has(normalizedKey)) {
184
+ suppressedKeyUps.delete(normalizedKey);
185
+ consume(e);
186
+ return;
187
+ }
175
188
  const { doubleTap } = cfg();
176
189
  if (!doubleTap.enabled)
177
190
  return;
@@ -198,13 +211,14 @@ export function useKeyboardShortcuts(options) {
198
211
  }
199
212
  function onBlurOrVisibility() {
200
213
  lastActivationKeyUpTime = 0;
214
+ suppressedKeyUps.clear();
201
215
  }
202
216
  function attach() {
203
217
  if (listenerAttached)
204
218
  return;
205
219
  listenerAttached = true;
206
- document.addEventListener("keydown", onKeyDown, true);
207
- document.addEventListener("keyup", onKeyUp, true);
220
+ window.addEventListener("keydown", onKeyDown, true);
221
+ window.addEventListener("keyup", onKeyUp, true);
208
222
  window.addEventListener("blur", onBlurOrVisibility);
209
223
  document.addEventListener("visibilitychange", onBlurOrVisibility);
210
224
  }
@@ -212,8 +226,8 @@ export function useKeyboardShortcuts(options) {
212
226
  if (!listenerAttached)
213
227
  return;
214
228
  listenerAttached = false;
215
- document.removeEventListener("keydown", onKeyDown, true);
216
- document.removeEventListener("keyup", onKeyUp, true);
229
+ window.removeEventListener("keydown", onKeyDown, true);
230
+ window.removeEventListener("keyup", onKeyUp, true);
217
231
  window.removeEventListener("blur", onBlurOrVisibility);
218
232
  document.removeEventListener("visibilitychange", onBlurOrVisibility);
219
233
  }
@@ -0,0 +1,21 @@
1
+ import type { Ref } from 'vue-demi';
2
+ import type { MentionCandidate } from '../utils/mention';
3
+ export declare function useMentionDropdown(inputEl: Ref<HTMLElement | null>, candidates: Ref<MentionCandidate[]>): {
4
+ isOpen: Ref<boolean, boolean>;
5
+ filteredCandidates: import("vue-demi").ComputedRef<MentionCandidate[]>;
6
+ activeIndex: Ref<number, number>;
7
+ dropdownPosition: Ref<{
8
+ x: number;
9
+ y: number;
10
+ }, {
11
+ x: number;
12
+ y: number;
13
+ } | {
14
+ x: number;
15
+ y: number;
16
+ }>;
17
+ checkForTrigger: () => void;
18
+ selectCandidate: (candidate: MentionCandidate) => void;
19
+ onKeyDown: (e: KeyboardEvent) => boolean;
20
+ close: () => void;
21
+ };
@@ -0,0 +1,162 @@
1
+ import { computed, ref } from "vue-demi";
2
+ function getSelection(el) {
3
+ const root = el.getRootNode();
4
+ if (root instanceof ShadowRoot && typeof root.getSelection === "function")
5
+ return root.getSelection();
6
+ return window.getSelection();
7
+ }
8
+ function findTrigger(el) {
9
+ const sel = getSelection(el);
10
+ if (!sel || sel.rangeCount === 0)
11
+ return null;
12
+ const range = sel.getRangeAt(0);
13
+ if (!range.collapsed)
14
+ return null;
15
+ const { startContainer, startOffset } = range;
16
+ if (startContainer.nodeType !== Node.TEXT_NODE)
17
+ return null;
18
+ const textNode = startContainer;
19
+ const text = textNode.textContent || "";
20
+ const beforeCursor = text.slice(0, startOffset);
21
+ for (let i = beforeCursor.length - 1; i >= 0; i--) {
22
+ const ch = beforeCursor[i];
23
+ if (ch === " " || ch === "\n")
24
+ return null;
25
+ if (ch === "@") {
26
+ if (i === 0 || beforeCursor[i - 1] === " " || beforeCursor[i - 1] === "\n") {
27
+ return {
28
+ textNode,
29
+ atIndex: i,
30
+ query: beforeCursor.slice(i + 1)
31
+ };
32
+ }
33
+ return null;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ export function useMentionDropdown(inputEl, candidates) {
39
+ const isOpen = ref(false);
40
+ const query = ref("");
41
+ const activeIndex = ref(0);
42
+ const dropdownPosition = ref({ x: 0, y: 0 });
43
+ let currentTrigger = null;
44
+ const filteredCandidates = computed(() => {
45
+ if (!isOpen.value)
46
+ return [];
47
+ const q = query.value.toLowerCase();
48
+ return candidates.value.filter((c) => {
49
+ if (!q)
50
+ return true;
51
+ return String(c.displayNumber).startsWith(q) || c.commentPreview.toLowerCase().includes(q);
52
+ });
53
+ });
54
+ function checkForTrigger() {
55
+ const el = inputEl.value;
56
+ if (!el)
57
+ return;
58
+ const trigger = findTrigger(el);
59
+ if (trigger) {
60
+ currentTrigger = trigger;
61
+ query.value = trigger.query;
62
+ activeIndex.value = 0;
63
+ isOpen.value = true;
64
+ updatePosition();
65
+ } else {
66
+ close();
67
+ }
68
+ }
69
+ function updatePosition() {
70
+ const el = inputEl.value;
71
+ if (!el || !currentTrigger)
72
+ return;
73
+ const sel = getSelection(el);
74
+ if (!sel || sel.rangeCount === 0)
75
+ return;
76
+ const range = sel.getRangeAt(0).cloneRange();
77
+ range.setStart(currentTrigger.textNode, currentTrigger.atIndex);
78
+ const rect = range.getBoundingClientRect();
79
+ const containerRect = el.closest(".__va-input")?.getBoundingClientRect();
80
+ if (!containerRect)
81
+ return;
82
+ const x = rect.left - containerRect.left;
83
+ const y = rect.bottom - containerRect.top + 4;
84
+ const viewportBottom = window.innerHeight;
85
+ const estimatedDropdownHeight = Math.min(filteredCandidates.value.length * 36 + 8, 200);
86
+ if (rect.bottom + estimatedDropdownHeight > viewportBottom) {
87
+ dropdownPosition.value = { x, y: rect.top - containerRect.top - estimatedDropdownHeight - 4 };
88
+ } else {
89
+ dropdownPosition.value = { x, y };
90
+ }
91
+ }
92
+ function selectCandidate(candidate) {
93
+ const el = inputEl.value;
94
+ if (!el || !currentTrigger)
95
+ return;
96
+ const sel = getSelection(el);
97
+ if (!sel || sel.rangeCount === 0)
98
+ return;
99
+ const { textNode, atIndex } = currentTrigger;
100
+ const cursorOffset = sel.getRangeAt(0).startOffset;
101
+ const range = document.createRange();
102
+ range.setStart(textNode, atIndex);
103
+ range.setEnd(textNode, cursorOffset);
104
+ range.deleteContents();
105
+ const chip = document.createElement("span");
106
+ chip.contentEditable = "false";
107
+ chip.className = "__va-mention";
108
+ chip.dataset.mentionId = candidate.id;
109
+ chip.textContent = `@${candidate.displayNumber}`;
110
+ range.insertNode(chip);
111
+ const space = document.createTextNode("\xA0");
112
+ chip.after(space);
113
+ const newRange = document.createRange();
114
+ newRange.setStartAfter(space);
115
+ newRange.collapse(true);
116
+ sel.removeAllRanges();
117
+ sel.addRange(newRange);
118
+ close();
119
+ el.dispatchEvent(new Event("input", { bubbles: true }));
120
+ }
121
+ function onKeyDown(e) {
122
+ if (!isOpen.value || filteredCandidates.value.length === 0)
123
+ return false;
124
+ if (e.key === "ArrowDown") {
125
+ e.preventDefault();
126
+ activeIndex.value = (activeIndex.value + 1) % filteredCandidates.value.length;
127
+ return true;
128
+ }
129
+ if (e.key === "ArrowUp") {
130
+ e.preventDefault();
131
+ activeIndex.value = (activeIndex.value - 1 + filteredCandidates.value.length) % filteredCandidates.value.length;
132
+ return true;
133
+ }
134
+ if (e.key === "Enter") {
135
+ e.preventDefault();
136
+ selectCandidate(filteredCandidates.value[activeIndex.value]);
137
+ return true;
138
+ }
139
+ if (e.key === "Escape") {
140
+ e.preventDefault();
141
+ close();
142
+ return true;
143
+ }
144
+ return false;
145
+ }
146
+ function close() {
147
+ isOpen.value = false;
148
+ query.value = "";
149
+ activeIndex.value = 0;
150
+ currentTrigger = null;
151
+ }
152
+ return {
153
+ isOpen,
154
+ filteredCandidates,
155
+ activeIndex,
156
+ dropdownPosition,
157
+ checkForTrigger,
158
+ selectCandidate,
159
+ onKeyDown,
160
+ close
161
+ };
162
+ }
@@ -1,2 +1,3 @@
1
1
  export declare const VA_DATA_ATTR = "data-agentation-vue";
2
2
  export declare const VA_DATA_ATTR_SELECTOR = "[data-agentation-vue]";
3
+ export declare const VA_VERSION = "0.2.10";
@@ -1,2 +1,3 @@
1
1
  export const VA_DATA_ATTR = "data-agentation-vue";
2
2
  export const VA_DATA_ATTR_SELECTOR = "[data-agentation-vue]";
3
+ export const VA_VERSION = "0.2.10";
@@ -1 +1 @@
1
- [data-agentation-vue]{--va-bg:#fff;--va-bg-secondary:#f5f5f5;--va-text:#1a1a1a;--va-text-secondary:#666;--va-border:#e5e5e5}[data-agentation-vue]:not([data-agentation-vue] [data-agentation-vue]){--va-accent:#42b883;--va-accent-rgb:66,184,131}@media (prefers-color-scheme:dark){[data-agentation-vue]:not([data-va-theme=light]){--va-bg:#1a1a1a;--va-bg-secondary:#2a2a2a;--va-text:#f5f5f5;--va-text-secondary:#999;--va-border:#333}}[data-agentation-vue][data-va-theme=dark],[data-agentation-vue][data-va-theme=dark] [data-agentation-vue]{--va-bg:#1a1a1a;--va-bg-secondary:#2a2a2a;--va-text:#f5f5f5;--va-text-secondary:#999;--va-border:#333}[data-agentation-vue][data-va-theme=light],[data-agentation-vue][data-va-theme=light] [data-agentation-vue]{--va-bg:#fff;--va-bg-secondary:#f5f5f5;--va-text:#1a1a1a;--va-text-secondary:#666;--va-border:#e5e5e5}:where([data-agentation-vue],[data-agentation-vue] *){border:none;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;line-height:1.5;list-style:none;margin:0;padding:0;text-decoration:none}.__va-intercept{background:transparent;cursor:crosshair;inset:0;position:fixed;z-index:2147483646}.__va-intercept--input-open{pointer-events:none}.__va-toolbar{position:fixed;--va-toolbar-base-transform:translate(0);--va-toolbar-auto-hide-transform:translate(0);--va-toolbar-size:42px;--va-toolbar-edge-offset:20px;--va-toolbar-auto-hide-peek:8px;--va-toolbar-auto-hide-corner-peek:16px;--va-toolbar-auto-hide-shift:calc(var(--va-toolbar-edge-offset) + var(--va-toolbar-size) - var(--va-toolbar-auto-hide-peek));--va-toolbar-auto-hide-corner-shift:calc(var(--va-toolbar-edge-offset) + var(--va-toolbar-size) - var(--va-toolbar-auto-hide-corner-peek));display:block;max-width:42px;min-height:42px;z-index:2147483647;--va-toolbar-resize-delay:0ms;background:var(--va-bg);border:1px solid var(--va-border);border-radius:999px;box-shadow:0 4px 12px rgba(0,0,0,.15);padding:0;transform:var(--va-toolbar-base-transform) var(--va-toolbar-auto-hide-transform);transition:border-radius .12s cubic-bezier(.32,.72,0,1),padding .12s cubic-bezier(.32,.72,0,1),max-width .24s cubic-bezier(.22,1,.36,1) var(--va-toolbar-resize-delay),transform .15s ease,opacity .15s ease,box-shadow .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}.__va-toolbar--place-bottom-right{bottom:20px;right:20px}.__va-toolbar--place-bottom-center{bottom:20px;left:50%;--va-toolbar-base-transform:translateX(-50%)}.__va-toolbar--place-bottom-left{bottom:20px;left:20px}.__va-toolbar--place-top-right{right:20px;top:20px}.__va-toolbar--place-top-center{left:50%;top:20px;--va-toolbar-base-transform:translateX(-50%)}.__va-toolbar--place-top-left{left:20px;top:20px}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-left{--va-toolbar-auto-hide-transform:translate(calc(var(--va-toolbar-auto-hide-corner-shift)*-1),calc(var(--va-toolbar-auto-hide-corner-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-center{--va-toolbar-auto-hide-transform:translateY(calc(var(--va-toolbar-auto-hide-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-right{--va-toolbar-auto-hide-transform:translate(var(--va-toolbar-auto-hide-corner-shift),calc(var(--va-toolbar-auto-hide-corner-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-left{--va-toolbar-auto-hide-transform:translate(calc(var(--va-toolbar-auto-hide-corner-shift)*-1),var(--va-toolbar-auto-hide-corner-shift))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-center{--va-toolbar-auto-hide-transform:translateY(var(--va-toolbar-auto-hide-shift))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-right{--va-toolbar-auto-hide-transform:translate(var(--va-toolbar-auto-hide-corner-shift),var(--va-toolbar-auto-hide-corner-shift))}.__va-toolbar--collapsed{cursor:pointer;max-width:42px;min-height:42px}.__va-toolbar--expanded{--va-toolbar-resize-delay:110ms;border-radius:10px;max-width:520px;padding:4px}.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide):hover{box-shadow:0 6px 16px rgba(0,0,0,.2)}.__va-toolbar--dragging{bottom:auto;left:0;right:auto;top:0;transform:none;transition:none}.__va-toolbar--dragging.__va-toolbar--collapsed:hover{transform:none}.__va-toolbar-stage{align-items:center;display:flex;gap:4px}.__va-toolbar-stage--toggle{inset:0;justify-content:center;opacity:1;pointer-events:auto;position:absolute;transform:scale(1);transition:opacity .11s ease,transform .18s cubic-bezier(.22,1,.36,1),visibility 0s linear 0s;visibility:visible}.__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:8px;filter:blur(4px);opacity:0;overflow:hidden;pointer-events:none;transform:translateX(var(--va-toolbar-menu-enter-x));transition:opacity .14s ease 0s,transform .22s cubic-bezier(.22,1,.36,1) 0s,visibility 0s linear .14s,filter 80ms ease 0s;visibility:hidden}.__va-toolbar--expanded .__va-toolbar-stage--toggle{opacity:0;pointer-events:none;transform:scale(.94);transition:opacity 90ms ease,transform .12s ease,visibility 0s linear 90ms;visibility:hidden}.__va-toolbar--expanded .__va-toolbar-stage--menu{filter:blur(0);opacity:1;pointer-events:auto;transform:translateX(0);transition:opacity .15s ease .15s,transform .24s cubic-bezier(.22,1,.36,1) .12s,visibility 0s linear 0s,filter .15s ease .2s;visibility:visible}.__va-toolbar--place-bottom-right .__va-toolbar-stage--menu,.__va-toolbar--place-top-right .__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:-8px}.__va-toolbar--place-bottom-center .__va-toolbar-stage--menu,.__va-toolbar--place-top-center .__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:0px}.__va-snap-zones{inset:0;pointer-events:none;position:fixed;z-index:2147483646}.__va-snap-zone{background:rgba(var(--va-accent-rgb),.035);border:1.5px dashed rgba(var(--va-accent-rgb),.18);border-radius:999px;box-shadow:0 0 0 1px rgba(var(--va-accent-rgb),.04);height:42px;position:fixed;transform:translate(-50%,-50%);transition:all .12s ease;width:42px}.__va-snap-zone--active{background:rgba(var(--va-accent-rgb),.09);border-color:rgba(var(--va-accent-rgb),.4)}.__va-snap-zone--rect{border-radius:8px;transform:none}.__va-snap-zone--rect.__va-snap-zone--active{transform:none}.__va-icon-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;outline:none;padding:0;transition:background .15s ease,color .15s ease;width:32px}.__va-icon-btn:hover{background:var(--va-bg-secondary);color:var(--va-text)}.__va-icon-btn--active{background:rgba(var(--va-accent-rgb),.15);color:var(--va-accent)}.__va-icon-btn--active:hover{background:rgba(var(--va-accent-rgb),.25)}.__va-icon-btn:disabled{cursor:not-allowed;opacity:.3}.__va-icon-btn:disabled:hover{background:transparent}.__va-icon-btn svg{height:18px;width:18px}.__va-toolbar-toggle{align-items:center;background:transparent;border:none;color:var(--va-accent);cursor:pointer;display:flex;height:100%;justify-content:center;line-height:0;outline:none;padding:0;position:relative;width:100%}.__va-toolbar-toggle svg{flex-shrink:0;height:22px;width:22px}.__va-toolbar-sep{background:var(--va-border);flex-shrink:0;height:20px;width:1px}.__va-drag-handle{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:grab;display:flex;flex-shrink:0;height:32px;justify-content:center;outline:none;padding:0;transition:all .15s ease;width:24px}.__va-drag-handle:hover{background:var(--va-bg-secondary);color:var(--va-text)}.__va-drag-handle:active{cursor:grabbing}.__va-drag-handle-dots{display:grid;gap:2.5px;grid-template-columns:repeat(2,3px);grid-template-rows:repeat(3,3px)}.__va-drag-handle-dots span{background:currentColor;border-radius:50%;height:3px;opacity:.6;width:3px}.__va-toolbar-badge{align-items:center;background:var(--va-accent);border-radius:9px;color:#fff;display:flex;font-size:11px;font-weight:600;height:18px;justify-content:center;min-width:18px;padding:0 4px;position:absolute;right:-6px;top:-6px}.__va-tooltip{align-items:center;background:#131518;border:1px solid hsla(0,0%,100%,.08);border-radius:10px;box-shadow:0 10px 24px rgba(0,0,0,.32),0 2px 6px rgba(0,0,0,.28);color:#dce1e7;display:inline-flex;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:11px;font-weight:500;gap:6px;left:0;line-height:1.15;max-width:min(360px,calc(100vw - 16px));opacity:0;padding:6px 9px;pointer-events:none;position:fixed;top:0;transform:translateY(4px) scale(.98);transform-origin:center bottom;transition:opacity .12s ease,transform .12s ease;white-space:nowrap;z-index:2147483647}.__va-tooltip[data-placement=bottom]{transform:translateY(-4px) scale(.98);transform-origin:center top}.__va-tooltip.__va-tooltip--visible{opacity:1}.__va-tooltip.__va-tooltip--visible[data-placement=bottom],.__va-tooltip.__va-tooltip--visible[data-placement=top]{transform:translateY(0) scale(1)}.__va-tooltip-label{overflow:hidden;text-overflow:ellipsis}.__va-tooltip-shortcut{align-items:center;background:hsla(0,0%,100%,.1);border:1px solid hsla(0,0%,100%,.18);border-radius:6px;color:#b5bec8;display:inline-flex;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:11px;font-weight:600;letter-spacing:.03em;line-height:1;min-height:18px;padding:2px 6px}.__va-tooltip-arrow{background:#131518;border:1px solid hsla(0,0%,100%,.08);border-left:none;border-top:none;height:10px;left:50%;position:absolute;transform:translateX(-50%) rotate(45deg);width:10px}.__va-tooltip[data-placement=top] .__va-tooltip-arrow{bottom:-5px}.__va-tooltip[data-placement=bottom] .__va-tooltip-arrow{top:-5px;transform:translateX(-50%) rotate(225deg)}.__va-highlight{background:rgba(var(--va-accent-rgb),.08);border:2px solid var(--va-accent);border-radius:4px;pointer-events:none;position:fixed;transition:all 80ms ease;z-index:2147483645}.__va-highlight-label{background:var(--va-accent);border-radius:4px;color:#fff;font-size:11px;font-weight:500;left:0;max-width:300px;overflow:hidden;padding:2px 8px;position:absolute;text-overflow:ellipsis;top:-28px;white-space:nowrap}.__va-highlight-label--chain{background:#1a1a1a;max-width:500px;overflow:visible;padding:4px 10px}.__va-marker{align-items:center;background:var(--va-accent);border-radius:50%;box-shadow:0 2px 6px rgba(0,0,0,.3);color:#fff;cursor:pointer;display:flex;font-size:12px;font-weight:700;height:24px;justify-content:center;position:absolute;transform:translate(-50%,-50%);transition:all .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:24px;z-index:2147483647}.__va-marker:hover{box-shadow:0 3px 10px rgba(0,0,0,.4);transform:translate(-50%,-50%) scale(1.2)}.__va-marker--fixed{position:fixed}.__va-marker--stale{opacity:.5}.__va-marker-plus{height:14px;stroke-width:3;width:14px}.__va-marker-pencil{display:none;height:12px;width:12px}.__va-marker:not(.__va-marker--pending):hover .__va-marker-number{display:none}.__va-marker:not(.__va-marker--pending):hover .__va-marker-pencil{display:block}.__va-marker--pending{animation:__va-pulse 1.5s ease-in-out infinite}.__va-marker--selection{border-radius:6px;height:26px;width:26px}@keyframes __va-pulse{0%,to{box-shadow:0 2px 6px rgba(0,0,0,.3)}50%{box-shadow:0 2px 12px rgba(var(--va-accent-rgb),.5)}}.__va-input{background:var(--va-bg);border:1px solid var(--va-border);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-width:360px;min-width:280px;padding:12px;position:fixed;z-index:2147483647}.__va-input-label{color:var(--va-text-secondary);display:block;font-size:12px;margin-bottom:8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__va-input-chain{margin-bottom:8px}.__va-input-styles{margin-bottom:10px}.__va-input-styles-summary{align-items:center;color:var(--va-text-secondary);cursor:pointer;display:flex;font-size:12px;gap:6px;min-width:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.__va-input-styles-summary::-webkit-details-marker{display:none}.__va-input-styles-summary:before{content:"▸";flex-shrink:0;font-size:11px;transition:transform .16s ease}.__va-input-styles[open] .__va-input-styles-summary:before{transform:rotate(90deg)}.__va-input-styles-block{background:var(--va-bg-secondary);border:1px solid var(--va-border);border-radius:6px;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11px;line-height:1.55;margin-top:8px;padding:8px 10px}.__va-input-styles-element{color:var(--va-text-secondary);display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__va-input-style-line{color:var(--va-text);word-break:break-word}.__va-input-style-prop{color:#a855f7}.__va-input-style-value{color:var(--va-text)}.__va-comp-chain{align-items:center;display:inline-flex;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11px;gap:6px}.__va-comp,.__va-comp-chain{white-space:nowrap}.__va-comp-bracket{opacity:.4}.__va-comp-chain--dark{color:#fff}.__va-comp-chain--light{color:var(--va-accent)}.__va-comp-ellipsis{font-size:11px;letter-spacing:1px;opacity:.5}.__va-comp-chain--collapsible{cursor:pointer;flex-wrap:wrap}.__va-comp-toggle{flex-shrink:0;font-size:10px;opacity:.6;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:10px}.__va-input input{background:var(--va-bg);border:1px solid var(--va-border);border-radius:6px;color:var(--va-text);font-size:13px;outline:none;padding:8px 10px;transition:border-color .15s ease;width:100%}.__va-input input:focus{border-color:var(--va-accent)}.__va-input-actions{align-items:center;display:flex;gap:8px;justify-content:space-between;margin-top:8px}.__va-input-actions-right{display:flex;gap:8px;margin-left:auto}.__va-input-delete-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:pointer;display:flex;height:32px;justify-content:center;outline:none;padding:0;transition:all .15s ease;width:32px}.__va-input-delete-btn svg{height:16px;width:16px}.__va-input-delete-btn:hover{background:var(--va-bg-secondary);color:#ef4444}.__va-btn{border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;outline:none;padding:6px 14px;transition:all .15s ease}.__va-btn--secondary{background:var(--va-bg-secondary);color:var(--va-text-secondary)}.__va-btn--secondary:hover{background:var(--va-border)}.__va-btn--primary{background:var(--va-accent);color:#fff}.__va-btn--primary:hover{opacity:.9}.__va-btn:disabled{cursor:not-allowed;opacity:.4}.__va-settings-popover{position:fixed;z-index:2147483647}.__va-settings{background:var(--va-bg);border:1px solid var(--va-border);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-width:min(340px,calc(100vw - 16px));min-width:260px;padding:16px;position:relative}.__va-settings-top{border-bottom:1px solid var(--va-border);display:flex;justify-content:flex-end;margin-bottom:12px;padding-bottom:10px}.__va-theme-toggle{align-items:center;background:var(--va-bg-secondary);border:1px solid var(--va-border);border-radius:999px;color:var(--va-text-secondary);cursor:pointer;display:inline-flex;height:28px;justify-content:center;transition:all .15s ease;width:28px}.__va-theme-toggle:hover{border-color:var(--va-text-secondary);color:var(--va-text)}.__va-theme-toggle svg{height:15px;width:15px}.__va-settings-row{align-items:center;display:flex;gap:12px;justify-content:space-between;margin-bottom:10px}.__va-settings-label{color:var(--va-text-secondary);font-size:12px;white-space:nowrap}.__va-settings-row--stack{display:block}.__va-settings-row--stack .__va-settings-label{display:block;margin-bottom:8px}.__va-settings-row:last-child{margin-bottom:0}.__va-settings-divider{background:var(--va-border);height:1px;margin:8px 0 12px}.__va-settings select{background:var(--va-bg);border:1px solid var(--va-border);border-radius:4px;color:var(--va-text);font-size:12px;outline:none;padding:4px 8px}.__va-color-swatches{align-items:center;display:flex;gap:6px}.__va-color-swatch{border:2px solid var(--va-border);border-radius:50%;cursor:pointer;flex-shrink:0;height:22px;outline:none;padding:0;transition:all .15s ease;width:22px}.__va-color-swatch:hover{transform:scale(1.15)}.__va-color-swatch--active{border-color:var(--va-text);transform:scale(1.15)}.__va-toggle{background:var(--va-border);border:none;border-radius:10px;cursor:pointer;flex-shrink:0;height:20px;outline:none;padding:0;position:relative;transition:background .15s ease;width:36px}.__va-toggle--active{background:var(--va-accent)}.__va-toggle:after{background:#fff;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.2);content:"";height:16px;left:2px;position:absolute;top:2px;transition:transform .15s ease;width:16px}.__va-toggle--active:after{transform:translateX(16px)}.__va-selection-rect{background:rgba(var(--va-accent-rgb),.06);border:2px dashed var(--va-accent);border-radius:2px;pointer-events:none;position:fixed;z-index:2147483645}.__va-copy-feedback{animation:__va-fadeIn .15s ease;background:var(--va-accent);border-radius:6px;bottom:72px;box-shadow:0 4px 12px rgba(0,0,0,.15);color:#fff;font-size:13px;font-weight:500;padding:8px 16px;position:fixed;right:20px;z-index:2147483647}@keyframes __va-fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
1
+ [data-agentation-vue]{--va-bg:#fff;--va-bg-secondary:#f5f5f5;--va-text:#1a1a1a;--va-text-secondary:#666;--va-border:#e5e5e5}[data-agentation-vue]:not([data-agentation-vue] [data-agentation-vue]){--va-accent:#42b883;--va-accent-rgb:66,184,131}@media (prefers-color-scheme:dark){[data-agentation-vue]:not([data-va-theme=light]){--va-bg:#1a1a1a;--va-bg-secondary:#2a2a2a;--va-text:#f5f5f5;--va-text-secondary:#999;--va-border:#333}}[data-agentation-vue][data-va-theme=dark],[data-agentation-vue][data-va-theme=dark] [data-agentation-vue]{--va-bg:#1a1a1a;--va-bg-secondary:#2a2a2a;--va-text:#f5f5f5;--va-text-secondary:#999;--va-border:#333}[data-agentation-vue][data-va-theme=light],[data-agentation-vue][data-va-theme=light] [data-agentation-vue]{--va-bg:#fff;--va-bg-secondary:#f5f5f5;--va-text:#1a1a1a;--va-text-secondary:#666;--va-border:#e5e5e5}:where([data-agentation-vue],[data-agentation-vue] *){border:none;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;line-height:1.5;list-style:none;margin:0;padding:0;text-decoration:none}.__va-intercept{background:transparent;cursor:crosshair;inset:0;position:fixed;z-index:2147483646}.__va-intercept--input-open{pointer-events:none}.__va-toolbar{position:fixed;--va-toolbar-base-transform:translate(0);--va-toolbar-auto-hide-transform:translate(0);--va-toolbar-size:42px;--va-toolbar-edge-offset:20px;--va-toolbar-auto-hide-peek:8px;--va-toolbar-auto-hide-corner-peek:16px;--va-toolbar-auto-hide-shift:calc(var(--va-toolbar-edge-offset) + var(--va-toolbar-size) - var(--va-toolbar-auto-hide-peek));--va-toolbar-auto-hide-corner-shift:calc(var(--va-toolbar-edge-offset) + var(--va-toolbar-size) - var(--va-toolbar-auto-hide-corner-peek));display:block;max-width:42px;min-height:42px;z-index:2147483647;--va-toolbar-resize-delay:0ms;background:var(--va-bg);border:1px solid var(--va-border);border-radius:999px;box-shadow:0 4px 12px rgba(0,0,0,.15);padding:0;transform:var(--va-toolbar-base-transform) var(--va-toolbar-auto-hide-transform);transition:border-radius .12s cubic-bezier(.32,.72,0,1),padding .12s cubic-bezier(.32,.72,0,1),max-width .24s cubic-bezier(.22,1,.36,1) var(--va-toolbar-resize-delay),transform .15s ease,opacity .15s ease,box-shadow .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}.__va-toolbar--place-bottom-right{bottom:20px;right:20px}.__va-toolbar--place-bottom-center{bottom:20px;left:50%;--va-toolbar-base-transform:translateX(-50%)}.__va-toolbar--place-bottom-left{bottom:20px;left:20px}.__va-toolbar--place-top-right{right:20px;top:20px}.__va-toolbar--place-top-center{left:50%;top:20px;--va-toolbar-base-transform:translateX(-50%)}.__va-toolbar--place-top-left{left:20px;top:20px}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-left{--va-toolbar-auto-hide-transform:translate(calc(var(--va-toolbar-auto-hide-corner-shift)*-1),calc(var(--va-toolbar-auto-hide-corner-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-center{--va-toolbar-auto-hide-transform:translateY(calc(var(--va-toolbar-auto-hide-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-top-right{--va-toolbar-auto-hide-transform:translate(var(--va-toolbar-auto-hide-corner-shift),calc(var(--va-toolbar-auto-hide-corner-shift)*-1))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-left{--va-toolbar-auto-hide-transform:translate(calc(var(--va-toolbar-auto-hide-corner-shift)*-1),var(--va-toolbar-auto-hide-corner-shift))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-center{--va-toolbar-auto-hide-transform:translateY(var(--va-toolbar-auto-hide-shift))}.__va-toolbar--auto-hide.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide-revealed).__va-toolbar--place-bottom-right{--va-toolbar-auto-hide-transform:translate(var(--va-toolbar-auto-hide-corner-shift),var(--va-toolbar-auto-hide-corner-shift))}.__va-toolbar--collapsed{cursor:pointer;max-width:42px;min-height:42px}.__va-toolbar--expanded{--va-toolbar-resize-delay:110ms;border-radius:10px;max-width:520px;padding:4px}.__va-toolbar--collapsed:not(.__va-toolbar--auto-hide):hover{box-shadow:0 6px 16px rgba(0,0,0,.2)}.__va-toolbar--dragging{bottom:auto;left:0;right:auto;top:0;transform:none;transition:none}.__va-toolbar--dragging.__va-toolbar--collapsed:hover{transform:none}.__va-toolbar-stage{align-items:center;display:flex;gap:4px}.__va-toolbar-stage--toggle{inset:0;justify-content:center;opacity:1;pointer-events:auto;position:absolute;transform:scale(1);transition:opacity .11s ease,transform .18s cubic-bezier(.22,1,.36,1),visibility 0s linear 0s;visibility:visible}.__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:8px;filter:blur(4px);opacity:0;overflow:hidden;pointer-events:none;transform:translateX(var(--va-toolbar-menu-enter-x));transition:opacity .14s ease 0s,transform .22s cubic-bezier(.22,1,.36,1) 0s,visibility 0s linear .14s,filter 80ms ease 0s;visibility:hidden}.__va-toolbar--expanded .__va-toolbar-stage--toggle{opacity:0;pointer-events:none;transform:scale(.94);transition:opacity 90ms ease,transform .12s ease,visibility 0s linear 90ms;visibility:hidden}.__va-toolbar--expanded .__va-toolbar-stage--menu{filter:blur(0);opacity:1;pointer-events:auto;transform:translateX(0);transition:opacity .15s ease .15s,transform .24s cubic-bezier(.22,1,.36,1) .12s,visibility 0s linear 0s,filter .15s ease .2s;visibility:visible}.__va-toolbar--place-bottom-right .__va-toolbar-stage--menu,.__va-toolbar--place-top-right .__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:-8px}.__va-toolbar--place-bottom-center .__va-toolbar-stage--menu,.__va-toolbar--place-top-center .__va-toolbar-stage--menu{--va-toolbar-menu-enter-x:0px}.__va-snap-zones{inset:0;pointer-events:none;position:fixed;z-index:2147483646}.__va-snap-zone{background:rgba(var(--va-accent-rgb),.035);border:1.5px dashed rgba(var(--va-accent-rgb),.18);border-radius:999px;box-shadow:0 0 0 1px rgba(var(--va-accent-rgb),.04);height:42px;position:fixed;transform:translate(-50%,-50%);transition:all .12s ease;width:42px}.__va-snap-zone--active{background:rgba(var(--va-accent-rgb),.09);border-color:rgba(var(--va-accent-rgb),.4)}.__va-snap-zone--rect{border-radius:8px;transform:none}.__va-snap-zone--rect.__va-snap-zone--active{transform:none}.__va-icon-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:pointer;display:flex;flex-shrink:0;height:32px;justify-content:center;outline:none;padding:0;transition:background .15s ease,color .15s ease;width:32px}.__va-icon-btn:hover{background:var(--va-bg-secondary);color:var(--va-text)}.__va-icon-btn--active{background:rgba(var(--va-accent-rgb),.15);color:var(--va-accent)}.__va-icon-btn--active:hover{background:rgba(var(--va-accent-rgb),.25)}.__va-icon-btn:disabled{cursor:not-allowed;opacity:.3}.__va-icon-btn:disabled:hover{background:transparent}.__va-icon-btn svg{height:18px;width:18px}.__va-toolbar-toggle{align-items:center;background:transparent;border:none;color:var(--va-accent);cursor:pointer;display:flex;height:100%;justify-content:center;line-height:0;outline:none;padding:0;position:relative;width:100%}.__va-toolbar-toggle svg{flex-shrink:0;height:22px;width:22px}.__va-toolbar-sep{background:var(--va-border);flex-shrink:0;height:20px;width:1px}.__va-drag-handle{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:grab;display:flex;flex-shrink:0;height:32px;justify-content:center;outline:none;padding:0;transition:all .15s ease;width:24px}.__va-drag-handle:hover{background:var(--va-bg-secondary);color:var(--va-text)}.__va-drag-handle:active{cursor:grabbing}.__va-drag-handle-dots{display:grid;gap:2.5px;grid-template-columns:repeat(2,3px);grid-template-rows:repeat(3,3px)}.__va-drag-handle-dots span{background:currentColor;border-radius:50%;height:3px;opacity:.6;width:3px}.__va-toolbar-badge{align-items:center;background:var(--va-accent);border-radius:9px;color:#fff;display:flex;font-size:11px;font-weight:600;height:18px;justify-content:center;min-width:18px;padding:0 4px;position:absolute;right:-6px;top:-6px}.__va-tooltip{align-items:center;background:#131518;border:1px solid hsla(0,0%,100%,.08);border-radius:10px;box-shadow:0 10px 24px rgba(0,0,0,.32),0 2px 6px rgba(0,0,0,.28);color:#dce1e7;display:inline-flex;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:11px;font-weight:500;gap:6px;left:0;line-height:1.15;max-width:min(360px,calc(100vw - 16px));opacity:0;padding:6px 9px;pointer-events:none;position:fixed;top:0;transform:translateY(4px) scale(.98);transform-origin:center bottom;transition:opacity .12s ease,transform .12s ease;white-space:nowrap;z-index:2147483647}.__va-tooltip[data-placement=bottom]{transform:translateY(-4px) scale(.98);transform-origin:center top}.__va-tooltip.__va-tooltip--visible{opacity:1}.__va-tooltip.__va-tooltip--visible[data-placement=bottom],.__va-tooltip.__va-tooltip--visible[data-placement=top]{transform:translateY(0) scale(1)}.__va-tooltip-label{overflow:hidden;text-overflow:ellipsis}.__va-tooltip-shortcut{align-items:center;background:hsla(0,0%,100%,.1);border:1px solid hsla(0,0%,100%,.18);border-radius:6px;color:#b5bec8;display:inline-flex;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:11px;font-weight:600;letter-spacing:.03em;line-height:1;min-height:18px;padding:2px 6px}.__va-tooltip-arrow{background:#131518;border:1px solid hsla(0,0%,100%,.08);border-left:none;border-top:none;height:10px;left:50%;position:absolute;transform:translateX(-50%) rotate(45deg);width:10px}.__va-tooltip[data-placement=top] .__va-tooltip-arrow{bottom:-5px}.__va-tooltip[data-placement=bottom] .__va-tooltip-arrow{top:-5px;transform:translateX(-50%) rotate(225deg)}.__va-highlight{background:rgba(var(--va-accent-rgb),.08);border:2px solid var(--va-accent);border-radius:4px;pointer-events:none;position:fixed;transition:all 80ms ease;z-index:2147483645}.__va-highlight-label{background:var(--va-accent);border-radius:4px;color:#fff;font-size:11px;font-weight:500;left:0;max-width:300px;overflow:hidden;padding:2px 8px;position:absolute;text-overflow:ellipsis;top:-28px;white-space:nowrap}.__va-highlight-label--chain{background:#1a1a1a;max-width:500px;overflow:visible;padding:4px 10px}.__va-marker{align-items:center;background:var(--va-accent);border-radius:50%;box-shadow:0 2px 6px rgba(0,0,0,.3);color:#fff;cursor:pointer;display:flex;font-size:12px;font-weight:700;height:24px;justify-content:center;position:absolute;transform:translate(-50%,-50%);transition:all .15s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:24px;z-index:2147483647}.__va-marker:hover{box-shadow:0 3px 10px rgba(0,0,0,.4);transform:translate(-50%,-50%) scale(1.2)}.__va-marker--fixed{position:fixed}.__va-marker--stale{opacity:.5}.__va-marker-plus{height:14px;stroke-width:3;width:14px}.__va-marker-pencil{display:none;height:12px;width:12px}.__va-marker:not(.__va-marker--pending):hover .__va-marker-number{display:none}.__va-marker:not(.__va-marker--pending):hover .__va-marker-pencil{display:block}.__va-marker--pending{animation:__va-pulse 1.5s ease-in-out infinite}.__va-marker--selection{border-radius:6px;height:26px;width:26px}@keyframes __va-pulse{0%,to{box-shadow:0 2px 6px rgba(0,0,0,.3)}50%{box-shadow:0 2px 12px rgba(var(--va-accent-rgb),.5)}}.__va-input{background:var(--va-bg);border:1px solid var(--va-border);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-width:360px;min-width:280px;padding:12px;position:fixed;z-index:2147483647}.__va-input-label{color:var(--va-text-secondary);display:block;font-size:12px;margin-bottom:8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__va-input-chain{margin-bottom:8px}.__va-input-styles{margin-bottom:10px}.__va-input-styles-summary{align-items:center;color:var(--va-text-secondary);cursor:pointer;display:flex;font-size:12px;gap:6px;min-width:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.__va-input-styles-summary::-webkit-details-marker{display:none}.__va-input-styles-summary:before{content:"▸";flex-shrink:0;font-size:11px;transition:transform .16s ease}.__va-input-styles[open] .__va-input-styles-summary:before{transform:rotate(90deg)}.__va-input-styles-block{background:var(--va-bg-secondary);border:1px solid var(--va-border);border-radius:6px;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11px;line-height:1.55;margin-top:8px;padding:8px 10px}.__va-input-styles-element{color:var(--va-text-secondary);display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__va-input-style-line{color:var(--va-text);word-break:break-word}.__va-input-style-prop{color:#a855f7}.__va-input-style-value{color:var(--va-text)}.__va-comp-chain{align-items:center;display:inline-flex;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11px;gap:6px}.__va-comp,.__va-comp-chain{white-space:nowrap}.__va-comp-bracket{opacity:.4}.__va-comp-chain--dark{color:#fff}.__va-comp-chain--light{color:var(--va-accent)}.__va-comp-ellipsis{font-size:11px;letter-spacing:1px;opacity:.5}.__va-comp-chain--collapsible{cursor:pointer;flex-wrap:wrap}.__va-comp-toggle{flex-shrink:0;font-size:10px;opacity:.6;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:10px}.__va-input-editable{background:var(--va-bg);border:1px solid var(--va-border);border-radius:6px;color:var(--va-text);font-family:inherit;font-size:13px;line-height:1.4;min-height:1.4em;outline:none;overflow:hidden;padding:8px 10px;transition:border-color .15s ease;white-space:pre-wrap;width:100%;word-break:break-word}.__va-input-editable:focus{border-color:var(--va-accent)}.__va-input-editable:empty:before{color:var(--va-text-secondary);content:attr(data-placeholder);pointer-events:none}.__va-mention{background:rgba(var(--va-accent-rgb),.15);border-radius:4px;color:var(--va-accent);cursor:default;display:inline;font-size:12px;font-weight:600;padding:1px 6px;-webkit-user-select:all;-moz-user-select:all;user-select:all;white-space:nowrap}.__va-mention--deleted{opacity:.45;text-decoration:line-through}.__va-mention-dropdown{background:var(--va-bg);border:1px solid var(--va-border);border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-height:200px;max-width:280px;min-width:180px;overflow-y:auto;padding:4px;position:absolute;z-index:1}.__va-mention-option{align-items:center;border-radius:4px;color:var(--va-text);cursor:pointer;display:flex;font-size:12px;gap:8px;padding:6px 8px}.__va-mention-option--active,.__va-mention-option:hover{background:var(--va-bg-secondary)}.__va-mention-option-number{align-items:center;background:var(--va-accent);border-radius:50%;color:#fff;display:inline-flex;flex-shrink:0;font-size:11px;font-weight:700;height:20px;justify-content:center;width:20px}.__va-mention-option-preview{color:var(--va-text-secondary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.__va-input-actions{align-items:center;display:flex;gap:8px;justify-content:space-between;margin-top:8px}.__va-input-actions-right{display:flex;gap:8px;margin-left:auto}.__va-input-delete-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--va-text-secondary);cursor:pointer;display:flex;height:32px;justify-content:center;outline:none;padding:0;transition:all .15s ease;width:32px}.__va-input-delete-btn svg{height:16px;width:16px}.__va-input-delete-btn:hover{background:var(--va-bg-secondary);color:#ef4444}.__va-btn{border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;outline:none;padding:6px 14px;transition:all .15s ease}.__va-btn--secondary{background:var(--va-bg-secondary);color:var(--va-text-secondary)}.__va-btn--secondary:hover{background:var(--va-border)}.__va-btn--primary{background:var(--va-accent);color:#fff}.__va-btn--primary:hover{opacity:.9}.__va-btn:disabled{cursor:not-allowed;opacity:.4}.__va-settings-popover{position:fixed;z-index:2147483647}.__va-settings{background:var(--va-bg);border:1px solid var(--va-border);border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,.15);max-width:min(340px,calc(100vw - 16px));min-width:260px;padding:16px;position:relative}.__va-settings-top{align-items:center;border-bottom:1px solid var(--va-border);display:flex;justify-content:space-between;margin-bottom:12px;padding-bottom:10px}.__va-settings-title{color:var(--va-text);font-size:13px;font-weight:600}.__va-settings-version{color:var(--va-text-secondary);font-size:11px;font-weight:400}.__va-theme-toggle{align-items:center;background:var(--va-bg-secondary);border:1px solid var(--va-border);border-radius:999px;color:var(--va-text-secondary);cursor:pointer;display:inline-flex;height:28px;justify-content:center;transition:all .15s ease;width:28px}.__va-theme-toggle:hover{border-color:var(--va-text-secondary);color:var(--va-text)}.__va-theme-toggle svg{height:15px;width:15px}.__va-settings-row{align-items:center;display:flex;gap:12px;justify-content:space-between;margin-bottom:10px}.__va-settings-label{color:var(--va-text-secondary);font-size:12px;white-space:nowrap}.__va-settings-row--stack{display:block}.__va-settings-row--stack .__va-settings-label{display:block;margin-bottom:8px}.__va-settings-row:last-child{margin-bottom:0}.__va-settings-divider{background:var(--va-border);height:1px;margin:8px 0 12px}.__va-settings select{background:var(--va-bg);border:1px solid var(--va-border);border-radius:4px;color:var(--va-text);font-size:12px;outline:none;padding:4px 8px}.__va-color-swatches{align-items:center;display:flex;gap:6px}.__va-color-swatch{border:2px solid var(--va-border);border-radius:50%;cursor:pointer;flex-shrink:0;height:22px;outline:none;padding:0;transition:all .15s ease;width:22px}.__va-color-swatch:hover{transform:scale(1.15)}.__va-color-swatch--active{border-color:var(--va-text);transform:scale(1.15)}.__va-toggle{background:var(--va-border);border:none;border-radius:10px;cursor:pointer;flex-shrink:0;height:20px;outline:none;padding:0;position:relative;transition:background .15s ease;width:36px}.__va-toggle--active{background:var(--va-accent)}.__va-toggle:after{background:#fff;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,.2);content:"";height:16px;left:2px;position:absolute;top:2px;transition:transform .15s ease;width:16px}.__va-toggle--active:after{transform:translateX(16px)}.__va-selection-rect{background:rgba(var(--va-accent-rgb),.06);border:2px dashed var(--va-accent);border-radius:2px;pointer-events:none;position:fixed;z-index:2147483645}.__va-copy-feedback{animation:__va-fadeIn .15s ease;background:var(--va-accent);border-radius:6px;bottom:72px;box-shadow:0 4px 12px rgba(0,0,0,.15);color:#fff;font-size:13px;font-weight:500;padding:8px 16px;position:fixed;right:20px;z-index:2147483647}@keyframes __va-fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.__va-undo-feedback{align-items:center;animation:__va-fadeIn .15s ease;background:var(--va-bg);border:1px solid var(--va-border);border-radius:6px;bottom:72px;box-shadow:0 4px 12px rgba(0,0,0,.15);color:var(--va-text);display:flex;font-size:13px;font-weight:500;gap:12px;padding:8px 12px 8px 16px;position:fixed;right:20px;z-index:2147483647}.__va-undo-feedback--shifted{bottom:112px}.__va-undo-btn{background:var(--va-accent);border:none;border-radius:4px;color:#fff;cursor:pointer;font-family:inherit;font-size:12px;font-weight:600;outline:none;padding:4px 10px;transition:opacity .15s ease}.__va-undo-btn:hover{opacity:.85}
@@ -0,0 +1,13 @@
1
+ export interface MentionCandidate {
2
+ id: string;
3
+ displayNumber: number;
4
+ commentPreview: string;
5
+ }
6
+ export declare function createMentionChipHTML(id: string, displayNumber: number): string;
7
+ export declare function createDeletedMentionChipHTML(id: string): string;
8
+ export declare function serializeMentions(el: HTMLElement): string;
9
+ export declare function hydrateMentions(text: string, annotations: {
10
+ id: string;
11
+ displayNumber: number;
12
+ }[]): string;
13
+ export declare function extractMentionIds(text: string): string[];
@@ -0,0 +1,46 @@
1
+ const MENTION_REGEX = /@\[(\d+)\]/g;
2
+ export function createMentionChipHTML(id, displayNumber) {
3
+ return `<span contenteditable="false" class="__va-mention" data-mention-id="${id}">@${displayNumber}</span>`;
4
+ }
5
+ export function createDeletedMentionChipHTML(id) {
6
+ return `<span contenteditable="false" class="__va-mention __va-mention--deleted" data-mention-id="${id}">@?</span>`;
7
+ }
8
+ export function serializeMentions(el) {
9
+ let result = "";
10
+ for (const node of el.childNodes) {
11
+ if (node.nodeType === Node.TEXT_NODE) {
12
+ result += node.textContent || "";
13
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
14
+ const element = node;
15
+ if (element.classList.contains("__va-mention") && element.dataset.mentionId) {
16
+ result += `@[${element.dataset.mentionId}]`;
17
+ } else if (element.tagName === "BR") {
18
+ result += "\n";
19
+ } else {
20
+ const inner = serializeMentions(element);
21
+ if (element.tagName === "DIV" && result.length > 0 && !result.endsWith("\n"))
22
+ result += "\n";
23
+ result += inner;
24
+ }
25
+ }
26
+ }
27
+ return result;
28
+ }
29
+ export function hydrateMentions(text, annotations) {
30
+ const lookup = new Map(annotations.map((a) => [a.id, a.displayNumber]));
31
+ return escapeHTML(text).replace(
32
+ /@\[(\d+)\]/g,
33
+ (_, id) => {
34
+ const displayNumber = lookup.get(id);
35
+ if (displayNumber != null)
36
+ return createMentionChipHTML(id, displayNumber);
37
+ return createDeletedMentionChipHTML(id);
38
+ }
39
+ );
40
+ }
41
+ export function extractMentionIds(text) {
42
+ return [...new Set(Array.from(text.matchAll(MENTION_REGEX), (m) => m[1]))];
43
+ }
44
+ function escapeHTML(str) {
45
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentation-vue",
3
- "version": "0.2.6",
3
+ "version": "0.2.10",
4
4
  "description": "Visual feedback tool for AI coding agents — Vue 2.7 & 3",
5
5
  "author": "Dorian Becker",
6
6
  "license": "PolyForm-Shield-1.0.0",