@immense/vue-pom-generator 1.0.69 → 1.0.71

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/dist/index.cjs CHANGED
@@ -35,6 +35,7 @@ const tsMorph = require("ts-morph");
35
35
  const types = require("@babel/types");
36
36
  const node_perf_hooks = require("node:perf_hooks");
37
37
  const vue = require("@vitejs/plugin-vue");
38
+ const dom = require("@floating-ui/dom");
38
39
  var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
39
40
  function _interopNamespaceDefault(e) {
40
41
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -9674,6 +9675,1400 @@ function createVuePomGeneratorPlugins(options = {}) {
9674
9675
  }
9675
9676
  return resultPlugins;
9676
9677
  }
9678
+ function normalizeInlineText(value) {
9679
+ const normalized = value?.replace(/\s+/g, " ").trim();
9680
+ return normalized || void 0;
9681
+ }
9682
+ function formatAnnotations(annotations, detail, pageUrl) {
9683
+ const shortUrl = pageUrl.replace(/^https?:\/\//, "");
9684
+ const lines = [];
9685
+ lines.push(`## Feedback — ${shortUrl}`);
9686
+ lines.push(`- **URL:** ${pageUrl}`);
9687
+ if (detail === "forensic" && typeof window !== "undefined") {
9688
+ lines.push(`- **Viewport:** ${window.innerWidth}x${window.innerHeight}`);
9689
+ }
9690
+ lines.push("");
9691
+ for (let i = 0; i < annotations.length; i += 1) {
9692
+ const annotation = annotations[i];
9693
+ const title = normalizeInlineText(annotation.comment) || annotation.targetLabel;
9694
+ lines.push(`### ${i + 1}. ${title}`);
9695
+ lines.push(`- **Source:** ${annotation.source || "Unable to find component file path."}`);
9696
+ if (annotation.component) {
9697
+ lines.push(`- **Component:** ${annotation.component}`);
9698
+ }
9699
+ lines.push(`- **Target:** \`${annotation.targetLabel}\``);
9700
+ const uiText = normalizeInlineText(annotation.uiText);
9701
+ if (uiText) {
9702
+ lines.push(`- **UI text:** \`${uiText}\``);
9703
+ }
9704
+ const locator = normalizeInlineText(annotation.locator);
9705
+ if (locator) {
9706
+ lines.push(`- **Locator:** ${locator}`);
9707
+ }
9708
+ if (detail === "forensic") {
9709
+ const domHint = normalizeInlineText(annotation.domHint);
9710
+ if (domHint) {
9711
+ lines.push(`- **DOM hint:** \`${domHint}\``);
9712
+ }
9713
+ }
9714
+ lines.push("");
9715
+ }
9716
+ return lines.join("\n");
9717
+ }
9718
+ function formatSingleAnnotationPreview(annotation, detail, pageUrl) {
9719
+ const formatted = formatAnnotations([annotation], detail, pageUrl);
9720
+ const headingIndex = formatted.indexOf("### 1.");
9721
+ return headingIndex >= 0 ? formatted.slice(headingIndex).trim() : formatted.trim();
9722
+ }
9723
+ const ANNOTATOR_ROOT_ATTR = "data-vpg-annotator-root";
9724
+ const ANNOTATOR_STYLES = `
9725
+ [${ANNOTATOR_ROOT_ATTR}] {
9726
+ --vpg-annotator-accent: #4f46e5;
9727
+ --vpg-annotator-accent-strong: #4338ca;
9728
+ --vpg-annotator-bg: rgba(15, 23, 42, 0.96);
9729
+ --vpg-annotator-bg-soft: rgba(30, 41, 59, 0.96);
9730
+ --vpg-annotator-border: rgba(148, 163, 184, 0.28);
9731
+ --vpg-annotator-text: #e2e8f0;
9732
+ --vpg-annotator-text-soft: #94a3b8;
9733
+ --vpg-annotator-shadow: 0 16px 40px rgba(15, 23, 42, 0.26);
9734
+ --vpg-annotator-radius: 0px;
9735
+ --vpg-annotator-edge-offset: 20px;
9736
+ color: var(--vpg-annotator-text);
9737
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
9738
+ }
9739
+
9740
+ [${ANNOTATOR_ROOT_ATTR}],
9741
+ [${ANNOTATOR_ROOT_ATTR}] *,
9742
+ [${ANNOTATOR_ROOT_ATTR}] *::before,
9743
+ [${ANNOTATOR_ROOT_ATTR}] *::after {
9744
+ box-sizing: border-box;
9745
+ }
9746
+
9747
+ .vpg-annotator-layer {
9748
+ position: fixed;
9749
+ inset: 0;
9750
+ pointer-events: none;
9751
+ }
9752
+
9753
+ .vpg-annotator-layer--markers {
9754
+ z-index: 2147483646;
9755
+ }
9756
+
9757
+ .vpg-annotator-layer--panels {
9758
+ z-index: 2147483647;
9759
+ }
9760
+
9761
+ .vpg-annotator-toolbar,
9762
+ .vpg-annotator-panel,
9763
+ .vpg-annotator-input,
9764
+ .vpg-annotator-settings,
9765
+ .vpg-annotator-toast {
9766
+ background: var(--vpg-annotator-bg);
9767
+ border: 1px solid var(--vpg-annotator-border);
9768
+ box-shadow: var(--vpg-annotator-shadow);
9769
+ backdrop-filter: blur(18px);
9770
+ }
9771
+
9772
+ .vpg-annotator-toolbar {
9773
+ position: fixed;
9774
+ right: var(--vpg-annotator-edge-offset);
9775
+ bottom: var(--vpg-annotator-edge-offset);
9776
+ z-index: 2147483647;
9777
+ display: flex;
9778
+ gap: 8px;
9779
+ align-items: center;
9780
+ padding: 8px;
9781
+ border-radius: var(--vpg-annotator-radius);
9782
+ user-select: none;
9783
+ }
9784
+
9785
+ .vpg-annotator-toolbar-handle,
9786
+ .vpg-annotator-btn {
9787
+ appearance: none;
9788
+ display: inline-flex;
9789
+ align-items: center;
9790
+ justify-content: center;
9791
+ min-width: 32px;
9792
+ min-height: 32px;
9793
+ padding: 7px 10px;
9794
+ border: 1px solid var(--vpg-annotator-border);
9795
+ border-radius: var(--vpg-annotator-radius);
9796
+ background: var(--vpg-annotator-bg-soft);
9797
+ color: var(--vpg-annotator-text);
9798
+ font-size: 12px;
9799
+ font-weight: 600;
9800
+ line-height: 1;
9801
+ }
9802
+
9803
+ .vpg-annotator-toolbar-handle {
9804
+ padding: 0;
9805
+ cursor: grab;
9806
+ touch-action: none;
9807
+ }
9808
+
9809
+ .vpg-annotator-toolbar--dragging,
9810
+ .vpg-annotator-toolbar--dragging * {
9811
+ cursor: grabbing !important;
9812
+ }
9813
+
9814
+ .vpg-annotator-btn {
9815
+ cursor: pointer;
9816
+ }
9817
+
9818
+ .vpg-annotator-btn--icon {
9819
+ width: 32px;
9820
+ padding: 0;
9821
+ }
9822
+
9823
+ .vpg-annotator-btn:hover,
9824
+ .vpg-annotator-toolbar-handle:hover {
9825
+ border-color: rgba(99, 102, 241, 0.45);
9826
+ }
9827
+
9828
+ .vpg-annotator-btn:disabled {
9829
+ opacity: 0.45;
9830
+ cursor: not-allowed;
9831
+ }
9832
+
9833
+ .vpg-annotator-btn--primary,
9834
+ .vpg-annotator-btn[aria-pressed="true"] {
9835
+ background: var(--vpg-annotator-accent);
9836
+ border-color: var(--vpg-annotator-accent);
9837
+ color: white;
9838
+ }
9839
+
9840
+ .vpg-annotator-icon {
9841
+ width: 16px;
9842
+ height: 16px;
9843
+ display: inline-flex;
9844
+ align-items: center;
9845
+ justify-content: center;
9846
+ }
9847
+
9848
+ .vpg-annotator-icon svg {
9849
+ width: 100%;
9850
+ height: 100%;
9851
+ display: block;
9852
+ fill: none;
9853
+ stroke: currentColor;
9854
+ stroke-width: 1.5;
9855
+ stroke-linecap: round;
9856
+ stroke-linejoin: round;
9857
+ }
9858
+
9859
+ .vpg-annotator-count {
9860
+ padding-left: 4px;
9861
+ white-space: nowrap;
9862
+ }
9863
+
9864
+ .vpg-annotator-panel,
9865
+ .vpg-annotator-input,
9866
+ .vpg-annotator-settings {
9867
+ position: fixed;
9868
+ z-index: 2147483647;
9869
+ min-width: 300px;
9870
+ max-width: min(560px, calc(100vw - 24px));
9871
+ border-radius: var(--vpg-annotator-radius);
9872
+ overflow: visible;
9873
+ pointer-events: auto;
9874
+ visibility: hidden;
9875
+ }
9876
+
9877
+ .vpg-annotator-settings {
9878
+ min-width: 260px;
9879
+ max-width: min(320px, calc(100vw - 24px));
9880
+ }
9881
+
9882
+ .vpg-annotator-arrow {
9883
+ position: absolute;
9884
+ z-index: 0;
9885
+ width: 14px;
9886
+ height: 14px;
9887
+ background: var(--vpg-annotator-bg);
9888
+ border: 1px solid var(--vpg-annotator-border);
9889
+ transform: rotate(45deg);
9890
+ }
9891
+
9892
+ .vpg-annotator-panel[data-placement^="top"] .vpg-annotator-arrow,
9893
+ .vpg-annotator-input[data-placement^="top"] .vpg-annotator-arrow,
9894
+ .vpg-annotator-settings[data-placement^="top"] .vpg-annotator-arrow {
9895
+ border-top: none;
9896
+ border-left: none;
9897
+ }
9898
+
9899
+ .vpg-annotator-panel[data-placement^="bottom"] .vpg-annotator-arrow,
9900
+ .vpg-annotator-input[data-placement^="bottom"] .vpg-annotator-arrow,
9901
+ .vpg-annotator-settings[data-placement^="bottom"] .vpg-annotator-arrow {
9902
+ border-right: none;
9903
+ border-bottom: none;
9904
+ }
9905
+
9906
+ .vpg-annotator-panel[data-placement^="left"] .vpg-annotator-arrow,
9907
+ .vpg-annotator-input[data-placement^="left"] .vpg-annotator-arrow,
9908
+ .vpg-annotator-settings[data-placement^="left"] .vpg-annotator-arrow {
9909
+ border-left: none;
9910
+ border-bottom: none;
9911
+ }
9912
+
9913
+ .vpg-annotator-panel[data-placement^="right"] .vpg-annotator-arrow,
9914
+ .vpg-annotator-input[data-placement^="right"] .vpg-annotator-arrow,
9915
+ .vpg-annotator-settings[data-placement^="right"] .vpg-annotator-arrow {
9916
+ border-top: none;
9917
+ border-right: none;
9918
+ }
9919
+
9920
+ .vpg-annotator-panel-body,
9921
+ .vpg-annotator-input-body,
9922
+ .vpg-annotator-settings-body {
9923
+ position: relative;
9924
+ z-index: 1;
9925
+ padding: 14px;
9926
+ }
9927
+
9928
+ .vpg-annotator-heading {
9929
+ margin: 0 0 6px;
9930
+ font-size: 13px;
9931
+ font-weight: 700;
9932
+ }
9933
+
9934
+ .vpg-annotator-subtle {
9935
+ margin: 0;
9936
+ font-size: 12px;
9937
+ color: var(--vpg-annotator-text-soft);
9938
+ }
9939
+
9940
+ .vpg-annotator-textarea,
9941
+ .vpg-annotator-comment,
9942
+ .vpg-annotator-select {
9943
+ width: 100%;
9944
+ border: 1px solid var(--vpg-annotator-border);
9945
+ border-radius: var(--vpg-annotator-radius);
9946
+ background: rgba(15, 23, 42, 0.72);
9947
+ color: var(--vpg-annotator-text);
9948
+ }
9949
+
9950
+ .vpg-annotator-textarea,
9951
+ .vpg-annotator-comment {
9952
+ padding: 10px 12px;
9953
+ font-size: 12px;
9954
+ line-height: 1.55;
9955
+ }
9956
+
9957
+ .vpg-annotator-textarea {
9958
+ min-height: 220px;
9959
+ margin-top: 12px;
9960
+ resize: vertical;
9961
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
9962
+ }
9963
+
9964
+ .vpg-annotator-comment {
9965
+ min-height: 92px;
9966
+ resize: vertical;
9967
+ font-family: inherit;
9968
+ }
9969
+
9970
+ .vpg-annotator-input-meta {
9971
+ min-height: 160px;
9972
+ margin-bottom: 10px;
9973
+ }
9974
+
9975
+ .vpg-annotator-row {
9976
+ display: flex;
9977
+ align-items: center;
9978
+ justify-content: space-between;
9979
+ gap: 12px;
9980
+ margin-top: 12px;
9981
+ }
9982
+
9983
+ .vpg-annotator-actions {
9984
+ display: flex;
9985
+ gap: 8px;
9986
+ justify-content: flex-end;
9987
+ margin-top: 12px;
9988
+ }
9989
+
9990
+ .vpg-annotator-field {
9991
+ display: grid;
9992
+ gap: 6px;
9993
+ margin-top: 12px;
9994
+ }
9995
+
9996
+ .vpg-annotator-shortcuts {
9997
+ display: grid;
9998
+ gap: 8px;
9999
+ }
10000
+
10001
+ .vpg-annotator-shortcut-row {
10002
+ display: flex;
10003
+ align-items: center;
10004
+ justify-content: space-between;
10005
+ gap: 12px;
10006
+ }
10007
+
10008
+ .vpg-annotator-label {
10009
+ font-size: 12px;
10010
+ font-weight: 600;
10011
+ color: var(--vpg-annotator-text-soft);
10012
+ }
10013
+
10014
+ .vpg-annotator-select,
10015
+ .vpg-annotator-checkbox {
10016
+ accent-color: var(--vpg-annotator-accent);
10017
+ }
10018
+
10019
+ .vpg-annotator-select {
10020
+ padding: 8px 10px;
10021
+ }
10022
+
10023
+ .vpg-annotator-kbd {
10024
+ min-width: 32px;
10025
+ padding: 4px 8px;
10026
+ border: 1px solid var(--vpg-annotator-border);
10027
+ border-radius: var(--vpg-annotator-radius);
10028
+ background: rgba(15, 23, 42, 0.72);
10029
+ color: var(--vpg-annotator-text);
10030
+ font: inherit;
10031
+ font-size: 11px;
10032
+ font-weight: 700;
10033
+ line-height: 1;
10034
+ text-align: center;
10035
+ }
10036
+
10037
+ .vpg-annotator-highlight {
10038
+ position: fixed;
10039
+ z-index: 2147483646;
10040
+ pointer-events: none;
10041
+ border: 2px solid var(--vpg-annotator-accent);
10042
+ border-radius: var(--vpg-annotator-radius);
10043
+ background: rgba(79, 70, 229, 0.08);
10044
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08);
10045
+ }
10046
+
10047
+ .vpg-annotator-highlight-label {
10048
+ position: absolute;
10049
+ left: 0;
10050
+ top: -30px;
10051
+ max-width: min(520px, calc(100vw - 24px));
10052
+ overflow: hidden;
10053
+ text-overflow: ellipsis;
10054
+ white-space: nowrap;
10055
+ border-radius: var(--vpg-annotator-radius);
10056
+ background: var(--vpg-annotator-accent);
10057
+ color: white;
10058
+ padding: 5px 10px;
10059
+ font-size: 12px;
10060
+ font-weight: 700;
10061
+ }
10062
+
10063
+ .vpg-annotator-shield {
10064
+ position: fixed;
10065
+ inset: 0;
10066
+ z-index: 2147483645;
10067
+ background: transparent;
10068
+ }
10069
+
10070
+ .vpg-annotator-marker {
10071
+ position: absolute;
10072
+ z-index: 2147483646;
10073
+ display: flex;
10074
+ align-items: center;
10075
+ justify-content: center;
10076
+ width: 24px;
10077
+ height: 24px;
10078
+ pointer-events: auto;
10079
+ border: none;
10080
+ border-radius: var(--vpg-annotator-radius);
10081
+ background: var(--vpg-annotator-accent);
10082
+ color: white;
10083
+ font-size: 12px;
10084
+ font-weight: 700;
10085
+ cursor: pointer;
10086
+ box-shadow: 0 8px 20px rgba(15, 23, 42, 0.28);
10087
+ }
10088
+
10089
+ .vpg-annotator-toast {
10090
+ position: fixed;
10091
+ right: 24px;
10092
+ bottom: 86px;
10093
+ z-index: 2147483647;
10094
+ padding: 8px 12px;
10095
+ border-radius: var(--vpg-annotator-radius);
10096
+ color: var(--vpg-annotator-text);
10097
+ font-size: 12px;
10098
+ font-weight: 600;
10099
+ }
10100
+ `;
10101
+ const ignoredComponentNamePattern = /^(?:items\[\d+\]\.template|template|anonymous|slot|transition|transition-group)$/i;
10102
+ function getDirectVueInstances(element) {
10103
+ const instances = [];
10104
+ const vue2Instance = element.__vue__;
10105
+ const vue3Instance = element.__vueParentComponent;
10106
+ if (vue2Instance) {
10107
+ instances.push(vue2Instance);
10108
+ }
10109
+ if (vue3Instance && vue3Instance !== vue2Instance) {
10110
+ instances.push(vue3Instance);
10111
+ }
10112
+ return instances;
10113
+ }
10114
+ function stripSourcePosition(sourcePath) {
10115
+ return sourcePath.replace(/:\d+:\d+$/, "");
10116
+ }
10117
+ function inferNameFromFile(filePath) {
10118
+ const fileName = stripSourcePosition(filePath).split("/").pop();
10119
+ if (!fileName) {
10120
+ return null;
10121
+ }
10122
+ return fileName.replace(/\.vue$/, "");
10123
+ }
10124
+ function isMeaningfulComponentName(name) {
10125
+ return !!name && !ignoredComponentNamePattern.test(name.trim());
10126
+ }
10127
+ function isComponentLikeSourceTag(tag) {
10128
+ return !!tag && (/[A-Z]/.test(tag.trim()) || tag.includes("-"));
10129
+ }
10130
+ function formatSourceLabel(sourcePath, includePosition = true) {
10131
+ const match = sourcePath.match(/^(.*?)(?::(\d+):(\d+))?$/);
10132
+ if (!match) {
10133
+ return sourcePath;
10134
+ }
10135
+ const [, rawPath, line, column] = match;
10136
+ const frontendPathMatch = rawPath.match(/(?:^|\/)frontend\/(src\/.*)$/);
10137
+ const normalizedPath = frontendPathMatch?.[1] ?? rawPath;
10138
+ if (includePosition && line && column) {
10139
+ return `${normalizedPath}:${line}:${column}`;
10140
+ }
10141
+ return normalizedPath;
10142
+ }
10143
+ function getStringPropValue(props, key) {
10144
+ const value = props?.[key];
10145
+ return typeof value === "string" && value.length > 0 ? value : void 0;
10146
+ }
10147
+ function getDisplayComponentName(componentName, sourcePath, sourceTag) {
10148
+ if (isComponentLikeSourceTag(sourceTag)) {
10149
+ return sourceTag.trim();
10150
+ }
10151
+ const inferredName = sourcePath ? inferNameFromFile(sourcePath) : null;
10152
+ if (isMeaningfulComponentName(inferredName)) {
10153
+ return inferredName;
10154
+ }
10155
+ if (isMeaningfulComponentName(componentName)) {
10156
+ return componentName.trim();
10157
+ }
10158
+ return void 0;
10159
+ }
10160
+ function resolveAnnotatedInfo(componentName, sourcePath, sourceTag) {
10161
+ const component = getDisplayComponentName(componentName, sourcePath, sourceTag);
10162
+ const source = sourcePath ? formatSourceLabel(sourcePath, true) : void 0;
10163
+ if (!component && !source) {
10164
+ return void 0;
10165
+ }
10166
+ return {
10167
+ component,
10168
+ source,
10169
+ formatted: component ? source ? `${component} (${source})` : component : source
10170
+ };
10171
+ }
10172
+ function getAnnotatedInfoFromElement(element, options) {
10173
+ const pomComponentAttribute = `${options.metadataAttributePrefix}-component`;
10174
+ const pomTagAttribute = `${options.metadataAttributePrefix}-tag`;
10175
+ const annotatedElement = element.closest(`[${pomComponentAttribute}], [${options.sourceAttribute}]`);
10176
+ if (!annotatedElement) {
10177
+ return void 0;
10178
+ }
10179
+ return resolveAnnotatedInfo(
10180
+ annotatedElement.getAttribute(pomComponentAttribute),
10181
+ annotatedElement.getAttribute(options.sourceAttribute) ?? void 0,
10182
+ annotatedElement.getAttribute(pomTagAttribute)
10183
+ );
10184
+ }
10185
+ function getAnnotatedInfoFromInstance(instance, options) {
10186
+ const pomComponentAttribute = `${options.metadataAttributePrefix}-component`;
10187
+ const pomTagAttribute = `${options.metadataAttributePrefix}-tag`;
10188
+ return resolveAnnotatedInfo(
10189
+ getStringPropValue(instance.vnode?.props, pomComponentAttribute),
10190
+ getStringPropValue(instance.vnode?.props, options.sourceAttribute),
10191
+ getStringPropValue(instance.vnode?.props, pomTagAttribute)
10192
+ );
10193
+ }
10194
+ function getInstanceName(instance) {
10195
+ if (instance.$options) {
10196
+ const name = instance.$options.name ?? instance.$options._componentTag ?? inferNameFromFile(instance.$options.__file ?? "");
10197
+ if (isMeaningfulComponentName(name)) {
10198
+ return {
10199
+ component: name,
10200
+ source: instance.$options.__file ? formatSourceLabel(instance.$options.__file, true) : void 0,
10201
+ formatted: instance.$options.__file ? `${name} (${formatSourceLabel(instance.$options.__file, true)})` : name
10202
+ };
10203
+ }
10204
+ }
10205
+ if (instance.type) {
10206
+ const name = instance.type.name ?? instance.type.__name ?? inferNameFromFile(instance.type.__file ?? "");
10207
+ if (isMeaningfulComponentName(name)) {
10208
+ return {
10209
+ component: name,
10210
+ source: instance.type.__file ? formatSourceLabel(instance.type.__file, true) : void 0,
10211
+ formatted: instance.type.__file ? `${name} (${formatSourceLabel(instance.type.__file, true)})` : name
10212
+ };
10213
+ }
10214
+ }
10215
+ return void 0;
10216
+ }
10217
+ function scoreInfo(info) {
10218
+ if (!info) {
10219
+ return -1;
10220
+ }
10221
+ return (info.component ? 10 : 0) + (info.source ? 100 : 0);
10222
+ }
10223
+ function resolveVueComponentInfo(element, options) {
10224
+ const annotatedElementInfo = getAnnotatedInfoFromElement(element, options);
10225
+ if (annotatedElementInfo) {
10226
+ return annotatedElementInfo;
10227
+ }
10228
+ const seenInstances = /* @__PURE__ */ new Set();
10229
+ let bestInfo;
10230
+ let bestScore = -1;
10231
+ let current = element;
10232
+ let depth = 0;
10233
+ while (current && current !== document.body && depth < 50) {
10234
+ for (const instance of getDirectVueInstances(current)) {
10235
+ if (seenInstances.has(instance)) {
10236
+ continue;
10237
+ }
10238
+ seenInstances.add(instance);
10239
+ const annotatedInstanceInfo = getAnnotatedInfoFromInstance(instance, options);
10240
+ const candidate = annotatedInstanceInfo ?? getInstanceName(instance);
10241
+ const score = scoreInfo(candidate);
10242
+ if (score > bestScore) {
10243
+ bestInfo = candidate;
10244
+ bestScore = score;
10245
+ }
10246
+ }
10247
+ current = current.parentElement;
10248
+ depth += 1;
10249
+ }
10250
+ return bestInfo;
10251
+ }
10252
+ const SETTINGS_STORAGE_KEY = "vpg-annotator-settings";
10253
+ const ANNOTATIONS_STORAGE_KEY = "vpg-annotator-annotations";
10254
+ const TOOLBAR_POSITION_STORAGE_KEY = "vpg-annotator-toolbar-position";
10255
+ const RUNTIME_GUARD = "__VUE_POM_GENERATOR_ANNOTATOR_RUNTIME__";
10256
+ const TOOLBAR_ICON_MARKUP = {
10257
+ drag: '<svg viewBox="0 0 16 16"><path d="M5 3h1v1H5zM10 3h1v1h-1zM5 7h1v1H5zM10 7h1v1h-1zM5 11h1v1H5zM10 11h1v1h-1z" fill="currentColor" stroke="none" /></svg>',
10258
+ inspect: '<svg viewBox="0 0 16 16"><path d="M8 2v3M8 11v3M2 8h3M11 8h3M4 4l2 2M10 10l2 2M12 4l-2 2M6 10l-2 2"/><circle cx="8" cy="8" r="2.5"/></svg>',
10259
+ preview: '<svg viewBox="0 0 16 16"><path d="M1.5 8s2.4-4 6.5-4 6.5 4 6.5 4-2.4 4-6.5 4-6.5-4-6.5-4Z"/><circle cx="8" cy="8" r="1.8"/></svg>',
10260
+ copy: '<svg viewBox="0 0 16 16"><path d="M6 2.5h6.5v9H6z"/><path d="M3.5 5.5H5v6.5h5.5v1.5h-7z"/></svg>',
10261
+ clear: '<svg viewBox="0 0 16 16"><path d="M2.5 4.5h11"/><path d="M6 2.5h4"/><path d="M5 4.5v8"/><path d="M8 4.5v8"/><path d="M11 4.5v8"/><path d="M4 4.5h8l-.6 9H4.6z"/></svg>',
10262
+ settings: '<svg viewBox="0 0 16 16"><path d="M8 2.2l1 .6 1.2-.2.8 1 .9.7-.3 1.2.5 1-.5 1 .3 1.2-.9.7-.8 1-1.2-.2-1 .6-1-.6-1.2.2-.8-1-.9-.7.3-1.2-.5-1 .5-1-.3-1.2.9-.7.8-1 1.2.2z"/><circle cx="8" cy="8" r="2.2"/></svg>'
10263
+ };
10264
+ const SHORTCUT_LABELS = {
10265
+ select: "S",
10266
+ preview: "P",
10267
+ copy: "C",
10268
+ clear: "X",
10269
+ settings: ",",
10270
+ cancel: "Esc"
10271
+ };
10272
+ function normalizeText(value) {
10273
+ const normalized = value?.replace(/\s+/g, " ").trim();
10274
+ return normalized || void 0;
10275
+ }
10276
+ function formatShortcutTitle(label, shortcut) {
10277
+ return shortcut ? `${label} (${shortcut})` : label;
10278
+ }
10279
+ function isInsideAnnotatorTree(node) {
10280
+ return node instanceof Element && !!node.closest(`[${ANNOTATOR_ROOT_ATTR}]`);
10281
+ }
10282
+ function isEditableTarget(node) {
10283
+ if (!(node instanceof Element)) {
10284
+ return false;
10285
+ }
10286
+ if (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement || node instanceof HTMLSelectElement) {
10287
+ return true;
10288
+ }
10289
+ return node.closest('[contenteditable=""], [contenteditable="true"]') !== null;
10290
+ }
10291
+ function getElementSummary(element) {
10292
+ const testId = element.getAttribute("data-testid");
10293
+ if (testId) {
10294
+ return `[data-testid="${testId}"]`;
10295
+ }
10296
+ const id = element.getAttribute("id");
10297
+ if (id) {
10298
+ return `${element.tagName.toLowerCase()}#${id}`;
10299
+ }
10300
+ const classes = Array.from(element.classList).slice(0, 2).join(".");
10301
+ return classes ? `${element.tagName.toLowerCase()}.${classes}` : element.tagName.toLowerCase();
10302
+ }
10303
+ function getElementPath(element) {
10304
+ const segments = [];
10305
+ let current = element;
10306
+ let depth = 0;
10307
+ while (current && current !== document.body && depth < 8) {
10308
+ const summary = getElementSummary(current);
10309
+ segments.unshift(summary);
10310
+ current = current.parentElement;
10311
+ depth += 1;
10312
+ }
10313
+ return segments.join(" > ");
10314
+ }
10315
+ function getNearbyText(element) {
10316
+ const ownText = normalizeText(element.textContent || void 0);
10317
+ if (ownText && ownText.length >= 2) {
10318
+ return ownText.length > 160 ? `${ownText.slice(0, 160)}...` : ownText;
10319
+ }
10320
+ const parentText = normalizeText(element.parentElement?.textContent || void 0);
10321
+ if (parentText) {
10322
+ return parentText.length > 160 ? `${parentText.slice(0, 160)}...` : parentText;
10323
+ }
10324
+ return void 0;
10325
+ }
10326
+ function getNearbyLocator(element) {
10327
+ const testId = element.getAttribute("data-testid") || element.getAttribute("data-v-pom-testid");
10328
+ if (testId) {
10329
+ return `near \`[data-testid="${testId}"]\``;
10330
+ }
10331
+ const siblings = Array.from(element.parentElement?.children || []).filter((sibling) => sibling !== element).slice(0, 3).map(getElementSummary);
10332
+ return siblings.length ? `near ${siblings.map((value) => `\`${value}\``).join(", ")}` : void 0;
10333
+ }
10334
+ function createFloatingArrow() {
10335
+ const arrowEl = document.createElement("div");
10336
+ arrowEl.className = "vpg-annotator-arrow";
10337
+ arrowEl.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10338
+ return arrowEl;
10339
+ }
10340
+ function toCssPixels(value) {
10341
+ return `${Math.round(value)}px`;
10342
+ }
10343
+ function clamp(value, min, max) {
10344
+ return Math.min(Math.max(value, min), max);
10345
+ }
10346
+ function createButtonBase(label, onClick, options = {}) {
10347
+ const button = document.createElement("button");
10348
+ button.type = "button";
10349
+ button.className = `vpg-annotator-btn${options.primary ? " vpg-annotator-btn--primary" : ""}`;
10350
+ button.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10351
+ button.setAttribute("aria-label", label);
10352
+ button.title = formatShortcutTitle(label, options.shortcut);
10353
+ if (options.shortcut) {
10354
+ button.setAttribute("aria-keyshortcuts", options.shortcut);
10355
+ }
10356
+ if (options.pressed) {
10357
+ button.setAttribute("aria-pressed", "true");
10358
+ }
10359
+ if (options.disabled) {
10360
+ button.disabled = true;
10361
+ }
10362
+ button.addEventListener("click", (event) => {
10363
+ event.preventDefault();
10364
+ event.stopPropagation();
10365
+ onClick();
10366
+ });
10367
+ return button;
10368
+ }
10369
+ function createIcon(iconName) {
10370
+ const icon = document.createElement("span");
10371
+ icon.className = "vpg-annotator-icon";
10372
+ icon.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10373
+ icon.setAttribute("aria-hidden", "true");
10374
+ icon.innerHTML = TOOLBAR_ICON_MARKUP[iconName];
10375
+ return icon;
10376
+ }
10377
+ function createButton(label, onClick, options = {}) {
10378
+ const button = createButtonBase(label, onClick, options);
10379
+ button.textContent = label;
10380
+ return button;
10381
+ }
10382
+ function createIconButton(label, iconName, onClick, options = {}) {
10383
+ const button = createButtonBase(label, onClick, options);
10384
+ button.classList.add("vpg-annotator-btn--icon");
10385
+ button.appendChild(createIcon(iconName));
10386
+ return button;
10387
+ }
10388
+ function createToolbarHandle() {
10389
+ const handle = document.createElement("div");
10390
+ handle.className = "vpg-annotator-toolbar-handle";
10391
+ handle.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10392
+ handle.setAttribute("title", "Drag annotator toolbar");
10393
+ handle.setAttribute("aria-hidden", "true");
10394
+ handle.appendChild(createIcon("drag"));
10395
+ return handle;
10396
+ }
10397
+ async function copyTextToClipboard(text) {
10398
+ if (navigator.clipboard?.writeText) {
10399
+ await navigator.clipboard.writeText(text);
10400
+ return;
10401
+ }
10402
+ const textarea = document.createElement("textarea");
10403
+ textarea.value = text;
10404
+ textarea.setAttribute("readonly", "true");
10405
+ textarea.style.position = "fixed";
10406
+ textarea.style.top = "0";
10407
+ textarea.style.left = "0";
10408
+ textarea.style.opacity = "0";
10409
+ document.body.appendChild(textarea);
10410
+ textarea.select();
10411
+ const copied = document.execCommand("copy");
10412
+ textarea.remove();
10413
+ if (!copied) {
10414
+ throw new Error("Clipboard copy failed.");
10415
+ }
10416
+ }
10417
+ class AnnotatorRuntime {
10418
+ options;
10419
+ settings;
10420
+ annotations = [];
10421
+ hoveredElement = null;
10422
+ inspectMode = false;
10423
+ pendingAnnotation = null;
10424
+ editingAnnotationId = null;
10425
+ toolbarEl;
10426
+ markerLayerEl;
10427
+ panelLayerEl;
10428
+ highlightEl;
10429
+ highlightLabelEl;
10430
+ shieldEl;
10431
+ toastEl = null;
10432
+ previewButton = null;
10433
+ settingsButton = null;
10434
+ currentPanelCleanup = null;
10435
+ currentPanelEl = null;
10436
+ toastTimer = null;
10437
+ toolbarPosition = null;
10438
+ constructor(options) {
10439
+ this.options = options;
10440
+ this.settings = this.loadSettings();
10441
+ this.annotations = this.loadAnnotations();
10442
+ this.toolbarPosition = this.loadToolbarPosition();
10443
+ }
10444
+ mount() {
10445
+ this.ensureStyles();
10446
+ this.createChrome();
10447
+ this.renderToolbar();
10448
+ this.renderMarkers();
10449
+ this.attachGlobalListeners();
10450
+ }
10451
+ ensureStyles() {
10452
+ if (document.getElementById("vpg-annotator-styles")) {
10453
+ return;
10454
+ }
10455
+ const style = document.createElement("style");
10456
+ style.id = "vpg-annotator-styles";
10457
+ style.textContent = ANNOTATOR_STYLES;
10458
+ document.head.appendChild(style);
10459
+ }
10460
+ createChrome() {
10461
+ this.toolbarEl = document.createElement("div");
10462
+ this.toolbarEl.className = "vpg-annotator-toolbar";
10463
+ this.toolbarEl.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10464
+ this.markerLayerEl = document.createElement("div");
10465
+ this.markerLayerEl.className = "vpg-annotator-layer vpg-annotator-layer--markers";
10466
+ this.markerLayerEl.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10467
+ this.panelLayerEl = document.createElement("div");
10468
+ this.panelLayerEl.className = "vpg-annotator-layer vpg-annotator-layer--panels";
10469
+ this.panelLayerEl.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10470
+ this.highlightEl = document.createElement("div");
10471
+ this.highlightEl.className = "vpg-annotator-highlight";
10472
+ this.highlightEl.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10473
+ this.highlightEl.hidden = true;
10474
+ this.highlightLabelEl = document.createElement("div");
10475
+ this.highlightLabelEl.className = "vpg-annotator-highlight-label";
10476
+ this.highlightLabelEl.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10477
+ this.highlightEl.appendChild(this.highlightLabelEl);
10478
+ this.panelLayerEl.appendChild(this.highlightEl);
10479
+ this.shieldEl = document.createElement("div");
10480
+ this.shieldEl.className = "vpg-annotator-shield";
10481
+ this.shieldEl.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10482
+ this.shieldEl.hidden = true;
10483
+ document.body.append(this.markerLayerEl, this.panelLayerEl, this.shieldEl, this.toolbarEl);
10484
+ }
10485
+ renderToolbar() {
10486
+ this.toolbarEl.replaceChildren();
10487
+ const handle = createToolbarHandle();
10488
+ this.attachToolbarDrag(handle);
10489
+ const count = document.createElement("span");
10490
+ count.className = "vpg-annotator-count vpg-annotator-subtle";
10491
+ count.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10492
+ count.textContent = `${this.annotations.length} annotation${this.annotations.length === 1 ? "" : "s"}`;
10493
+ const selectButton = createIconButton(this.inspectMode ? "Stop selecting" : "Select element", "inspect", () => {
10494
+ this.setInspectMode(!this.inspectMode);
10495
+ }, { primary: this.inspectMode, pressed: this.inspectMode, shortcut: SHORTCUT_LABELS.select });
10496
+ const previewButton = createIconButton("Preview annotations", "preview", () => this.openPreview(), {
10497
+ disabled: this.annotations.length === 0,
10498
+ shortcut: SHORTCUT_LABELS.preview
10499
+ });
10500
+ this.previewButton = previewButton;
10501
+ const copyButton = createIconButton("Copy annotations", "copy", () => this.copyAnnotations(), {
10502
+ disabled: this.annotations.length === 0,
10503
+ shortcut: SHORTCUT_LABELS.copy
10504
+ });
10505
+ const clearButton = createIconButton("Clear annotations", "clear", () => this.clearAnnotations(), {
10506
+ disabled: this.annotations.length === 0,
10507
+ shortcut: SHORTCUT_LABELS.clear
10508
+ });
10509
+ const settingsButton = createIconButton("Annotator settings", "settings", () => this.openSettings(), {
10510
+ shortcut: SHORTCUT_LABELS.settings
10511
+ });
10512
+ this.settingsButton = settingsButton;
10513
+ this.toolbarEl.append(handle, selectButton, previewButton, copyButton, clearButton, settingsButton, count);
10514
+ this.applyToolbarPosition();
10515
+ }
10516
+ renderMarkers() {
10517
+ this.markerLayerEl.replaceChildren();
10518
+ for (const [index, annotation] of this.annotations.entries()) {
10519
+ const marker = document.createElement("button");
10520
+ marker.type = "button";
10521
+ marker.className = "vpg-annotator-marker";
10522
+ marker.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10523
+ marker.textContent = String(index + 1);
10524
+ marker.style.left = toCssPixels(annotation.pageX);
10525
+ marker.style.top = toCssPixels(annotation.pageY - window.scrollY);
10526
+ marker.addEventListener("click", (event) => {
10527
+ event.preventDefault();
10528
+ event.stopPropagation();
10529
+ this.openInputForExistingAnnotation(annotation, marker);
10530
+ });
10531
+ this.markerLayerEl.appendChild(marker);
10532
+ }
10533
+ }
10534
+ attachGlobalListeners() {
10535
+ this.shieldEl.addEventListener("mousemove", (event) => this.onShieldMouseMove(event));
10536
+ this.shieldEl.addEventListener("click", (event) => this.onShieldClick(event));
10537
+ window.addEventListener("scroll", () => this.renderMarkers(), true);
10538
+ window.addEventListener("resize", () => {
10539
+ this.renderMarkers();
10540
+ this.applyToolbarPosition();
10541
+ });
10542
+ window.addEventListener("keydown", (event) => this.onWindowKeyDown(event), true);
10543
+ }
10544
+ loadSettings() {
10545
+ try {
10546
+ const raw = localStorage.getItem(SETTINGS_STORAGE_KEY);
10547
+ const parsed = raw ? JSON.parse(raw) : {};
10548
+ return {
10549
+ outputDetail: parsed.outputDetail ?? this.options.outputDetail,
10550
+ copyToClipboard: parsed.copyToClipboard ?? this.options.copyToClipboard,
10551
+ showComponentTree: parsed.showComponentTree ?? this.options.showComponentTree
10552
+ };
10553
+ } catch {
10554
+ return {
10555
+ outputDetail: this.options.outputDetail,
10556
+ copyToClipboard: this.options.copyToClipboard,
10557
+ showComponentTree: this.options.showComponentTree
10558
+ };
10559
+ }
10560
+ }
10561
+ saveSettings() {
10562
+ localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(this.settings));
10563
+ this.renderToolbar();
10564
+ }
10565
+ loadAnnotations() {
10566
+ try {
10567
+ const raw = sessionStorage.getItem(ANNOTATIONS_STORAGE_KEY);
10568
+ if (!raw) {
10569
+ return [];
10570
+ }
10571
+ const parsed = JSON.parse(raw);
10572
+ if (Array.isArray(parsed)) {
10573
+ return parsed;
10574
+ }
10575
+ return Array.isArray(parsed[window.location.href]) ? parsed[window.location.href] : [];
10576
+ } catch {
10577
+ return [];
10578
+ }
10579
+ }
10580
+ saveAnnotations() {
10581
+ const store = { [window.location.href]: this.annotations };
10582
+ sessionStorage.setItem(ANNOTATIONS_STORAGE_KEY, JSON.stringify(store));
10583
+ this.renderToolbar();
10584
+ this.renderMarkers();
10585
+ }
10586
+ loadToolbarPosition() {
10587
+ try {
10588
+ const raw = localStorage.getItem(TOOLBAR_POSITION_STORAGE_KEY);
10589
+ if (!raw) {
10590
+ return null;
10591
+ }
10592
+ const parsed = JSON.parse(raw);
10593
+ if (typeof parsed.left !== "number" || typeof parsed.top !== "number") {
10594
+ return null;
10595
+ }
10596
+ return { left: parsed.left, top: parsed.top };
10597
+ } catch {
10598
+ return null;
10599
+ }
10600
+ }
10601
+ saveToolbarPosition() {
10602
+ try {
10603
+ if (!this.toolbarPosition) {
10604
+ localStorage.removeItem(TOOLBAR_POSITION_STORAGE_KEY);
10605
+ return;
10606
+ }
10607
+ localStorage.setItem(TOOLBAR_POSITION_STORAGE_KEY, JSON.stringify(this.toolbarPosition));
10608
+ } catch {
10609
+ }
10610
+ }
10611
+ clampToolbarPosition(position) {
10612
+ const margin = 12;
10613
+ const width = this.toolbarEl.offsetWidth || 0;
10614
+ const height = this.toolbarEl.offsetHeight || 0;
10615
+ const maxLeft = Math.max(margin, window.innerWidth - width - margin);
10616
+ const maxTop = Math.max(margin, window.innerHeight - height - margin);
10617
+ return {
10618
+ left: clamp(position.left, margin, maxLeft),
10619
+ top: clamp(position.top, margin, maxTop)
10620
+ };
10621
+ }
10622
+ applyToolbarPosition() {
10623
+ if (!this.toolbarPosition) {
10624
+ this.toolbarEl.style.left = "";
10625
+ this.toolbarEl.style.top = "";
10626
+ this.toolbarEl.style.right = "";
10627
+ this.toolbarEl.style.bottom = "";
10628
+ return;
10629
+ }
10630
+ const position = this.clampToolbarPosition(this.toolbarPosition);
10631
+ this.toolbarPosition = position;
10632
+ this.toolbarEl.style.left = toCssPixels(position.left);
10633
+ this.toolbarEl.style.top = toCssPixels(position.top);
10634
+ this.toolbarEl.style.right = "auto";
10635
+ this.toolbarEl.style.bottom = "auto";
10636
+ }
10637
+ attachToolbarDrag(handle) {
10638
+ handle.addEventListener("pointerdown", (event) => {
10639
+ if (event.button !== 0) {
10640
+ return;
10641
+ }
10642
+ event.preventDefault();
10643
+ event.stopPropagation();
10644
+ const rect = this.toolbarEl.getBoundingClientRect();
10645
+ const startX = event.clientX;
10646
+ const startY = event.clientY;
10647
+ const startLeft = rect.left;
10648
+ const startTop = rect.top;
10649
+ this.toolbarEl.classList.add("vpg-annotator-toolbar--dragging");
10650
+ const onPointerMove = (moveEvent) => {
10651
+ this.toolbarPosition = {
10652
+ left: startLeft + moveEvent.clientX - startX,
10653
+ top: startTop + moveEvent.clientY - startY
10654
+ };
10655
+ this.applyToolbarPosition();
10656
+ };
10657
+ const onPointerUp = () => {
10658
+ this.toolbarEl.classList.remove("vpg-annotator-toolbar--dragging");
10659
+ window.removeEventListener("pointermove", onPointerMove);
10660
+ window.removeEventListener("pointerup", onPointerUp);
10661
+ window.removeEventListener("pointercancel", onPointerUp);
10662
+ this.saveToolbarPosition();
10663
+ };
10664
+ window.addEventListener("pointermove", onPointerMove);
10665
+ window.addEventListener("pointerup", onPointerUp);
10666
+ window.addEventListener("pointercancel", onPointerUp);
10667
+ });
10668
+ }
10669
+ onWindowKeyDown(event) {
10670
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) {
10671
+ return;
10672
+ }
10673
+ if (event.key === "Escape") {
10674
+ if (!this.inspectMode && !this.currentPanelEl) {
10675
+ return;
10676
+ }
10677
+ event.preventDefault();
10678
+ if (this.inspectMode) {
10679
+ this.setInspectMode(false);
10680
+ } else {
10681
+ this.closePanel();
10682
+ }
10683
+ return;
10684
+ }
10685
+ if (isEditableTarget(event.target)) {
10686
+ return;
10687
+ }
10688
+ const key = event.key.toLowerCase();
10689
+ if (key === SHORTCUT_LABELS.select.toLowerCase()) {
10690
+ event.preventDefault();
10691
+ this.setInspectMode(!this.inspectMode);
10692
+ return;
10693
+ }
10694
+ if (key === SHORTCUT_LABELS.preview.toLowerCase()) {
10695
+ if (!this.annotations.length) {
10696
+ return;
10697
+ }
10698
+ event.preventDefault();
10699
+ this.openPreview();
10700
+ return;
10701
+ }
10702
+ if (key === SHORTCUT_LABELS.copy.toLowerCase()) {
10703
+ if (!this.annotations.length) {
10704
+ return;
10705
+ }
10706
+ event.preventDefault();
10707
+ void this.copyAnnotations();
10708
+ return;
10709
+ }
10710
+ if (key === SHORTCUT_LABELS.clear.toLowerCase()) {
10711
+ if (!this.annotations.length) {
10712
+ return;
10713
+ }
10714
+ event.preventDefault();
10715
+ this.clearAnnotations();
10716
+ return;
10717
+ }
10718
+ if (event.key === SHORTCUT_LABELS.settings) {
10719
+ event.preventDefault();
10720
+ this.openSettings();
10721
+ }
10722
+ }
10723
+ setInspectMode(next) {
10724
+ this.inspectMode = next;
10725
+ this.shieldEl.hidden = !next;
10726
+ this.highlightEl.hidden = !next;
10727
+ if (!next) {
10728
+ this.hoveredElement = null;
10729
+ this.highlightEl.hidden = true;
10730
+ }
10731
+ this.closePanel();
10732
+ this.renderToolbar();
10733
+ }
10734
+ stopInspectModePreservingPending() {
10735
+ this.inspectMode = false;
10736
+ this.shieldEl.hidden = true;
10737
+ this.highlightEl.hidden = true;
10738
+ this.hoveredElement = null;
10739
+ this.renderToolbar();
10740
+ }
10741
+ elementFromPoint(clientX, clientY) {
10742
+ const previousPointerEvents = this.shieldEl.style.pointerEvents;
10743
+ this.shieldEl.style.pointerEvents = "none";
10744
+ const elements = document.elementsFromPoint(clientX, clientY);
10745
+ this.shieldEl.style.pointerEvents = previousPointerEvents;
10746
+ return elements.find((element) => !isInsideAnnotatorTree(element)) ?? null;
10747
+ }
10748
+ onShieldMouseMove(event) {
10749
+ if (!this.inspectMode) {
10750
+ return;
10751
+ }
10752
+ const element = this.elementFromPoint(event.clientX, event.clientY);
10753
+ if (!element) {
10754
+ this.hoveredElement = null;
10755
+ this.highlightEl.hidden = true;
10756
+ return;
10757
+ }
10758
+ this.hoveredElement = element;
10759
+ const rect = element.getBoundingClientRect();
10760
+ const info = resolveVueComponentInfo(element, this.options);
10761
+ Object.assign(this.highlightEl.style, {
10762
+ left: toCssPixels(rect.left),
10763
+ top: toCssPixels(rect.top),
10764
+ width: toCssPixels(rect.width),
10765
+ height: toCssPixels(rect.height)
10766
+ });
10767
+ this.highlightLabelEl.textContent = info?.formatted || getElementSummary(element);
10768
+ this.highlightEl.hidden = false;
10769
+ }
10770
+ onShieldClick(event) {
10771
+ if (!this.inspectMode) {
10772
+ return;
10773
+ }
10774
+ event.preventDefault();
10775
+ event.stopPropagation();
10776
+ const element = this.elementFromPoint(event.clientX, event.clientY);
10777
+ if (!element) {
10778
+ return;
10779
+ }
10780
+ this.hoveredElement = element;
10781
+ this.pendingAnnotation = this.buildAnnotationDraft(element, "");
10782
+ this.editingAnnotationId = null;
10783
+ this.stopInspectModePreservingPending();
10784
+ this.openInputForDraft(element);
10785
+ }
10786
+ buildAnnotationDraft(element, comment) {
10787
+ const rect = element.getBoundingClientRect();
10788
+ const componentInfo = resolveVueComponentInfo(element, this.options);
10789
+ return {
10790
+ id: `draft-${Date.now()}`,
10791
+ comment,
10792
+ component: this.settings.showComponentTree ? componentInfo?.component : void 0,
10793
+ source: componentInfo?.source,
10794
+ targetLabel: componentInfo?.component || getElementSummary(element),
10795
+ uiText: getNearbyText(element),
10796
+ locator: getNearbyLocator(element),
10797
+ domHint: getElementPath(element),
10798
+ pageX: rect.left + rect.width / 2,
10799
+ pageY: rect.top + window.scrollY
10800
+ };
10801
+ }
10802
+ createPanelBase(className) {
10803
+ const panel = document.createElement("div");
10804
+ panel.className = className;
10805
+ panel.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10806
+ const arrowEl = createFloatingArrow();
10807
+ const body = document.createElement("div");
10808
+ body.className = className === "vpg-annotator-settings" ? "vpg-annotator-settings-body" : className === "vpg-annotator-input" ? "vpg-annotator-input-body" : "vpg-annotator-panel-body";
10809
+ body.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10810
+ panel.append(arrowEl, body);
10811
+ document.body.appendChild(panel);
10812
+ return { panel, arrowEl, body };
10813
+ }
10814
+ attachFloating(reference, panel, arrowEl, placement, allowedPlacements) {
10815
+ const cleanup = dom.autoUpdate(reference, panel, async () => {
10816
+ const result = await dom.computePosition(reference, panel, {
10817
+ strategy: "fixed",
10818
+ placement,
10819
+ middleware: [
10820
+ dom.offset(14),
10821
+ dom.autoPlacement({ allowedPlacements: [...allowedPlacements], padding: 12 }),
10822
+ dom.shift({ padding: 12 }),
10823
+ dom.arrow({ element: arrowEl, padding: 10 })
10824
+ ]
10825
+ });
10826
+ panel.setAttribute("data-placement", result.placement);
10827
+ panel.style.left = toCssPixels(result.x);
10828
+ panel.style.top = toCssPixels(result.y);
10829
+ panel.style.visibility = "visible";
10830
+ const arrowData = result.middlewareData.arrow;
10831
+ const side = result.placement.split("-")[0];
10832
+ const staticSide = side === "top" ? "bottom" : side === "bottom" ? "top" : side === "left" ? "right" : "left";
10833
+ arrowEl.style.removeProperty("top");
10834
+ arrowEl.style.removeProperty("right");
10835
+ arrowEl.style.removeProperty("bottom");
10836
+ arrowEl.style.removeProperty("left");
10837
+ if (typeof arrowData?.x === "number") {
10838
+ arrowEl.style.left = toCssPixels(arrowData.x);
10839
+ }
10840
+ if (typeof arrowData?.y === "number") {
10841
+ arrowEl.style.top = toCssPixels(arrowData.y);
10842
+ }
10843
+ arrowEl.style.setProperty(staticSide, "-7px");
10844
+ });
10845
+ return cleanup;
10846
+ }
10847
+ openInputForDraft(reference) {
10848
+ if (!this.pendingAnnotation) {
10849
+ return;
10850
+ }
10851
+ const { panel, body, arrowEl } = this.createPanelBase("vpg-annotator-input");
10852
+ const title = document.createElement("div");
10853
+ title.className = "vpg-annotator-heading";
10854
+ title.textContent = this.pendingAnnotation.component || this.pendingAnnotation.targetLabel;
10855
+ const subtitle = document.createElement("p");
10856
+ subtitle.className = "vpg-annotator-subtle";
10857
+ subtitle.textContent = this.pendingAnnotation.source || "Unable to find component file path.";
10858
+ const metadata = document.createElement("textarea");
10859
+ metadata.className = "vpg-annotator-textarea vpg-annotator-input-meta";
10860
+ metadata.readOnly = true;
10861
+ metadata.value = formatSingleAnnotationPreview(this.pendingAnnotation, this.settings.outputDetail, window.location.href);
10862
+ const commentLabel = document.createElement("label");
10863
+ commentLabel.className = "vpg-annotator-label";
10864
+ commentLabel.textContent = "Comment";
10865
+ const comment = document.createElement("textarea");
10866
+ comment.className = "vpg-annotator-comment";
10867
+ comment.value = this.pendingAnnotation.comment;
10868
+ comment.placeholder = "Add a comment...";
10869
+ const actions = document.createElement("div");
10870
+ actions.className = "vpg-annotator-actions";
10871
+ const cancelButton = createButton("Cancel", () => this.closePanel());
10872
+ const saveButton = createButton(this.editingAnnotationId ? "Save" : "Add", () => {
10873
+ if (!this.pendingAnnotation) {
10874
+ return;
10875
+ }
10876
+ this.pendingAnnotation.comment = comment.value.trim();
10877
+ if (this.editingAnnotationId) {
10878
+ const index = this.annotations.findIndex((annotation) => annotation.id === this.editingAnnotationId);
10879
+ if (index >= 0) {
10880
+ this.annotations[index] = { ...this.pendingAnnotation, id: this.editingAnnotationId };
10881
+ }
10882
+ } else {
10883
+ this.annotations.push({ ...this.pendingAnnotation, id: `annotation-${Date.now()}` });
10884
+ }
10885
+ this.saveAnnotations();
10886
+ this.closePanel();
10887
+ }, { primary: true });
10888
+ actions.append(cancelButton);
10889
+ if (this.editingAnnotationId) {
10890
+ const deleteButton = createButton("Delete", () => {
10891
+ this.annotations = this.annotations.filter((annotation) => annotation.id !== this.editingAnnotationId);
10892
+ this.saveAnnotations();
10893
+ this.closePanel();
10894
+ });
10895
+ actions.append(deleteButton);
10896
+ }
10897
+ actions.append(saveButton);
10898
+ body.append(title, subtitle, metadata, commentLabel, comment, actions);
10899
+ this.showPanel(panel, () => this.attachFloating(reference, panel, arrowEl, "right-start", ["right-start", "left-start", "bottom-start", "top-start"]));
10900
+ comment.focus();
10901
+ }
10902
+ openInputForExistingAnnotation(annotation, reference) {
10903
+ this.pendingAnnotation = { ...annotation };
10904
+ this.editingAnnotationId = annotation.id;
10905
+ this.openInputForDraft(reference);
10906
+ }
10907
+ openPreview() {
10908
+ if (!this.previewButton || this.annotations.length === 0) {
10909
+ return;
10910
+ }
10911
+ const { panel, body, arrowEl } = this.createPanelBase("vpg-annotator-panel");
10912
+ const title = document.createElement("div");
10913
+ title.className = "vpg-annotator-heading";
10914
+ title.textContent = "Annotation preview";
10915
+ const subtitle = document.createElement("p");
10916
+ subtitle.className = "vpg-annotator-subtle";
10917
+ subtitle.textContent = "Exact text that Copy will copy.";
10918
+ const textarea = document.createElement("textarea");
10919
+ textarea.className = "vpg-annotator-textarea";
10920
+ textarea.readOnly = true;
10921
+ textarea.value = formatAnnotations(this.annotations, this.settings.outputDetail, window.location.href);
10922
+ body.append(title, subtitle, textarea);
10923
+ this.showPanel(panel, () => this.attachFloating(this.previewButton, panel, arrowEl, "top-start", ["top-start", "left-start", "top-end", "left-end"]));
10924
+ }
10925
+ openSettings() {
10926
+ if (!this.settingsButton) {
10927
+ return;
10928
+ }
10929
+ const { panel, body, arrowEl } = this.createPanelBase("vpg-annotator-settings");
10930
+ const title = document.createElement("div");
10931
+ title.className = "vpg-annotator-heading";
10932
+ title.textContent = "Annotator settings";
10933
+ const detailField = document.createElement("div");
10934
+ detailField.className = "vpg-annotator-field";
10935
+ const detailLabel = document.createElement("label");
10936
+ detailLabel.className = "vpg-annotator-label";
10937
+ detailLabel.textContent = "Output detail";
10938
+ const detailSelect = document.createElement("select");
10939
+ detailSelect.className = "vpg-annotator-select";
10940
+ detailSelect.innerHTML = '<option value="standard">Standard</option><option value="forensic">Forensic</option>';
10941
+ detailSelect.value = this.settings.outputDetail;
10942
+ detailSelect.addEventListener("change", () => {
10943
+ this.settings.outputDetail = detailSelect.value === "forensic" ? "forensic" : "standard";
10944
+ this.saveSettings();
10945
+ });
10946
+ detailField.append(detailLabel, detailSelect);
10947
+ const showComponentField = document.createElement("label");
10948
+ showComponentField.className = "vpg-annotator-row";
10949
+ const showComponentCopy = document.createElement("span");
10950
+ showComponentCopy.className = "vpg-annotator-label";
10951
+ showComponentCopy.textContent = "Show component labels";
10952
+ const showComponentCheckbox = document.createElement("input");
10953
+ showComponentCheckbox.type = "checkbox";
10954
+ showComponentCheckbox.className = "vpg-annotator-checkbox";
10955
+ showComponentCheckbox.checked = this.settings.showComponentTree;
10956
+ showComponentCheckbox.addEventListener("change", () => {
10957
+ this.settings.showComponentTree = showComponentCheckbox.checked;
10958
+ this.saveSettings();
10959
+ });
10960
+ showComponentField.append(showComponentCopy, showComponentCheckbox);
10961
+ const shortcutsField = document.createElement("div");
10962
+ shortcutsField.className = "vpg-annotator-field";
10963
+ const shortcutsLabel = document.createElement("div");
10964
+ shortcutsLabel.className = "vpg-annotator-label";
10965
+ shortcutsLabel.textContent = "Keyboard shortcuts";
10966
+ const shortcutsList = document.createElement("div");
10967
+ shortcutsList.className = "vpg-annotator-shortcuts";
10968
+ for (const [shortcut, description] of [
10969
+ [SHORTCUT_LABELS.select, "Toggle selection mode"],
10970
+ [SHORTCUT_LABELS.preview, "Open preview"],
10971
+ [SHORTCUT_LABELS.copy, "Copy annotations"],
10972
+ [SHORTCUT_LABELS.clear, "Clear annotations"],
10973
+ [SHORTCUT_LABELS.settings, "Open settings"],
10974
+ [SHORTCUT_LABELS.cancel, "Close panel / cancel selection"]
10975
+ ]) {
10976
+ const row = document.createElement("div");
10977
+ row.className = "vpg-annotator-shortcut-row";
10978
+ const text = document.createElement("span");
10979
+ text.className = "vpg-annotator-subtle";
10980
+ text.textContent = description;
10981
+ const kbd = document.createElement("kbd");
10982
+ kbd.className = "vpg-annotator-kbd";
10983
+ kbd.setAttribute(ANNOTATOR_ROOT_ATTR, "");
10984
+ kbd.textContent = shortcut;
10985
+ row.append(text, kbd);
10986
+ shortcutsList.appendChild(row);
10987
+ }
10988
+ shortcutsField.append(shortcutsLabel, shortcutsList);
10989
+ body.append(title, detailField, showComponentField, shortcutsField);
10990
+ this.showPanel(panel, () => this.attachFloating(this.settingsButton, panel, arrowEl, "top-start", ["top-start", "left-start", "top-end", "left-end"]));
10991
+ }
10992
+ disposeCurrentPanel() {
10993
+ this.currentPanelCleanup?.();
10994
+ this.currentPanelCleanup = null;
10995
+ this.currentPanelEl?.remove();
10996
+ this.currentPanelEl = null;
10997
+ }
10998
+ showPanel(panel, attachFloatingFn) {
10999
+ this.disposeCurrentPanel();
11000
+ this.currentPanelEl = panel;
11001
+ this.currentPanelCleanup = attachFloatingFn();
11002
+ const onPointerDown = (event) => {
11003
+ const target = event.target;
11004
+ if (isInsideAnnotatorTree(target)) {
11005
+ return;
11006
+ }
11007
+ this.closePanel();
11008
+ };
11009
+ document.addEventListener("pointerdown", onPointerDown, true);
11010
+ const originalCleanup = this.currentPanelCleanup;
11011
+ this.currentPanelCleanup = () => {
11012
+ originalCleanup?.();
11013
+ document.removeEventListener("pointerdown", onPointerDown, true);
11014
+ };
11015
+ }
11016
+ closePanel() {
11017
+ this.disposeCurrentPanel();
11018
+ this.pendingAnnotation = null;
11019
+ this.editingAnnotationId = null;
11020
+ }
11021
+ clearAnnotations() {
11022
+ if (!this.annotations.length) {
11023
+ return;
11024
+ }
11025
+ this.annotations = [];
11026
+ this.saveAnnotations();
11027
+ this.showToast("Annotations cleared");
11028
+ this.closePanel();
11029
+ }
11030
+ async copyAnnotations() {
11031
+ if (!this.annotations.length) {
11032
+ return;
11033
+ }
11034
+ const text = formatAnnotations(this.annotations, this.settings.outputDetail, window.location.href);
11035
+ try {
11036
+ await copyTextToClipboard(text);
11037
+ this.showToast("Copied");
11038
+ } catch {
11039
+ this.showToast("Copy failed");
11040
+ }
11041
+ }
11042
+ showToast(message) {
11043
+ this.toastEl?.remove();
11044
+ if (this.toastTimer !== null) {
11045
+ window.clearTimeout(this.toastTimer);
11046
+ }
11047
+ const toast = document.createElement("div");
11048
+ toast.className = "vpg-annotator-toast";
11049
+ toast.setAttribute(ANNOTATOR_ROOT_ATTR, "");
11050
+ toast.textContent = message;
11051
+ document.body.appendChild(toast);
11052
+ this.toastEl = toast;
11053
+ this.toastTimer = window.setTimeout(() => {
11054
+ toast.remove();
11055
+ if (this.toastEl === toast) {
11056
+ this.toastEl = null;
11057
+ }
11058
+ this.toastTimer = null;
11059
+ }, 1600);
11060
+ }
11061
+ }
11062
+ function mountAnnotatorClient(options) {
11063
+ const runtimeWindow = window;
11064
+ if (runtimeWindow[RUNTIME_GUARD]) {
11065
+ return runtimeWindow[RUNTIME_GUARD];
11066
+ }
11067
+ const runtime = new AnnotatorRuntime(options);
11068
+ runtime.mount();
11069
+ runtimeWindow[RUNTIME_GUARD] = runtime;
11070
+ return runtime;
11071
+ }
9677
11072
  const nuxtConfigMarker = /* @__PURE__ */ Symbol.for("@immense/vue-pom-generator.nuxt");
9678
11073
  function defineVuePomGeneratorConfig(options) {
9679
11074
  return options;
@@ -9690,5 +11085,6 @@ exports.createVuePomGeneratorPlugins = createVuePomGeneratorPlugins;
9690
11085
  exports.default = createVuePomGeneratorPlugins;
9691
11086
  exports.defineNuxtPomGeneratorConfig = defineNuxtPomGeneratorConfig;
9692
11087
  exports.defineVuePomGeneratorConfig = defineVuePomGeneratorConfig;
11088
+ exports.mountAnnotatorClient = mountAnnotatorClient;
9693
11089
  exports.vuePomGenerator = createVuePomGeneratorPlugins;
9694
11090
  //# sourceMappingURL=index.cjs.map