@akccakcctw/vue-grab 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/api.d.ts +28 -0
- package/dist/core/api.js +105 -0
- package/dist/core/identifier.d.ts +2 -0
- package/dist/core/identifier.js +101 -0
- package/dist/core/overlay.d.ts +24 -0
- package/dist/core/overlay.js +509 -0
- package/dist/core/widget.d.ts +10 -0
- package/dist/core/widget.js +251 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +7 -0
- package/dist/nuxt/module.d.ts +8 -0
- package/dist/nuxt/module.js +40 -0
- package/dist/nuxt/runtime/plugin.d.ts +2 -0
- package/dist/nuxt/runtime/plugin.js +14 -0
- package/dist/plugin.d.ts +11 -0
- package/dist/plugin.js +47 -0
- package/dist/vite.d.ts +8 -0
- package/dist/vite.js +198 -0
- package/package.json +33 -8
- package/.github/release-please-config.json +0 -9
- package/.github/release-please-manifest.json +0 -3
- package/.github/workflows/release.yml +0 -37
- package/AGENTS.md +0 -75
- package/README.md +0 -116
- package/akccakcctw-vue-grab-1.0.0.tgz +0 -0
- package/docs/SDD.md +0 -188
- package/src/__tests__/plugin.spec.ts +0 -60
- package/src/core/__tests__/api.spec.ts +0 -178
- package/src/core/__tests__/identifier.spec.ts +0 -126
- package/src/core/__tests__/overlay.spec.ts +0 -431
- package/src/core/__tests__/widget.spec.ts +0 -57
- package/src/core/api.ts +0 -144
- package/src/core/identifier.ts +0 -89
- package/src/core/overlay.ts +0 -348
- package/src/core/widget.ts +0 -289
- package/src/index.ts +0 -8
- package/src/nuxt/module.ts +0 -102
- package/src/nuxt/runtime/plugin.ts +0 -13
- package/src/plugin.ts +0 -48
- package/tsconfig.json +0 -44
- package/vitest.config.ts +0 -9
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { OverlayOptions } from './overlay.js';
|
|
2
|
+
export interface ComponentInfo {
|
|
3
|
+
name: string;
|
|
4
|
+
file: string;
|
|
5
|
+
props: Record<string, any>;
|
|
6
|
+
data: Record<string, any>;
|
|
7
|
+
element: HTMLElement;
|
|
8
|
+
line?: number;
|
|
9
|
+
column?: number;
|
|
10
|
+
vnode?: any;
|
|
11
|
+
}
|
|
12
|
+
export type VueGrabOptions = OverlayOptions;
|
|
13
|
+
export interface VueGrabAPI {
|
|
14
|
+
activate(): void;
|
|
15
|
+
deactivate(): void;
|
|
16
|
+
readonly isActive: boolean;
|
|
17
|
+
grabAt(x: number, y: number): ComponentInfo | null;
|
|
18
|
+
grabFromSelector(selector: string): ComponentInfo | null;
|
|
19
|
+
grabFromElement(element: Element): ComponentInfo | null;
|
|
20
|
+
highlight(selector: string): void;
|
|
21
|
+
enable(): void;
|
|
22
|
+
disable(): void;
|
|
23
|
+
getComponentDetails(selectorOrElement: string | Element): ComponentInfo | null;
|
|
24
|
+
setOverlayStyle(style: Record<string, string>): void;
|
|
25
|
+
setDomFileResolver(resolver: VueGrabOptions['domFileResolver']): void;
|
|
26
|
+
}
|
|
27
|
+
export declare function createVueGrabAPI(targetWindow: Window, options?: VueGrabOptions): VueGrabAPI;
|
|
28
|
+
export declare function installVueGrab(targetWindow: Window, options?: VueGrabOptions): VueGrabAPI;
|
package/dist/core/api.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { extractMetadata, identifyComponent } from './identifier.js';
|
|
2
|
+
import { createOverlayController } from './overlay.js';
|
|
3
|
+
import { createToggleWidget } from './widget.js';
|
|
4
|
+
function getComponentInfo(el, resolver) {
|
|
5
|
+
if (!el)
|
|
6
|
+
return null;
|
|
7
|
+
const instance = identifyComponent(el);
|
|
8
|
+
const metadata = extractMetadata(instance, el);
|
|
9
|
+
if (!metadata)
|
|
10
|
+
return null;
|
|
11
|
+
const fallback = !instance && resolver ? resolver(el) : null;
|
|
12
|
+
if (fallback?.file)
|
|
13
|
+
metadata.file = fallback.file;
|
|
14
|
+
if (typeof fallback?.line === 'number')
|
|
15
|
+
metadata.line = fallback.line;
|
|
16
|
+
if (typeof fallback?.column === 'number')
|
|
17
|
+
metadata.column = fallback.column;
|
|
18
|
+
return {
|
|
19
|
+
...metadata,
|
|
20
|
+
element: el
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function createVueGrabAPI(targetWindow, options = {}) {
|
|
24
|
+
let active = false;
|
|
25
|
+
let domFileResolver = options.domFileResolver;
|
|
26
|
+
const overlay = createOverlayController(targetWindow, {
|
|
27
|
+
...options,
|
|
28
|
+
onAfterCopy: () => {
|
|
29
|
+
api.deactivate();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const widget = createToggleWidget(targetWindow, {
|
|
33
|
+
onToggle(nextActive) {
|
|
34
|
+
if (nextActive) {
|
|
35
|
+
api.activate();
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
api.deactivate();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const api = {
|
|
43
|
+
activate() {
|
|
44
|
+
active = true;
|
|
45
|
+
overlay.start();
|
|
46
|
+
widget.setActive(true);
|
|
47
|
+
},
|
|
48
|
+
deactivate() {
|
|
49
|
+
active = false;
|
|
50
|
+
overlay.stop();
|
|
51
|
+
widget.setActive(false);
|
|
52
|
+
},
|
|
53
|
+
get isActive() {
|
|
54
|
+
return active;
|
|
55
|
+
},
|
|
56
|
+
grabAt(x, y) {
|
|
57
|
+
if (typeof targetWindow.document.elementFromPoint !== 'function')
|
|
58
|
+
return null;
|
|
59
|
+
const el = targetWindow.document.elementFromPoint(x, y);
|
|
60
|
+
return getComponentInfo(el, domFileResolver);
|
|
61
|
+
},
|
|
62
|
+
grabFromSelector(selector) {
|
|
63
|
+
const el = targetWindow.document.querySelector(selector);
|
|
64
|
+
return getComponentInfo(el, domFileResolver);
|
|
65
|
+
},
|
|
66
|
+
grabFromElement(element) {
|
|
67
|
+
return getComponentInfo(element, domFileResolver);
|
|
68
|
+
},
|
|
69
|
+
highlight(selector) {
|
|
70
|
+
const el = targetWindow.document.querySelector(selector);
|
|
71
|
+
overlay.highlight(el);
|
|
72
|
+
},
|
|
73
|
+
enable() {
|
|
74
|
+
this.activate();
|
|
75
|
+
},
|
|
76
|
+
disable() {
|
|
77
|
+
this.deactivate();
|
|
78
|
+
},
|
|
79
|
+
getComponentDetails(selectorOrElement) {
|
|
80
|
+
if (typeof selectorOrElement === 'string') {
|
|
81
|
+
const el = targetWindow.document.querySelector(selectorOrElement);
|
|
82
|
+
return getComponentInfo(el, domFileResolver);
|
|
83
|
+
}
|
|
84
|
+
return getComponentInfo(selectorOrElement, domFileResolver);
|
|
85
|
+
},
|
|
86
|
+
setOverlayStyle(style) {
|
|
87
|
+
overlay.setStyle(style);
|
|
88
|
+
},
|
|
89
|
+
setDomFileResolver(resolver) {
|
|
90
|
+
domFileResolver = resolver;
|
|
91
|
+
overlay.setDomFileResolver(domFileResolver);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
widget.mount();
|
|
95
|
+
widget.setActive(active);
|
|
96
|
+
return api;
|
|
97
|
+
}
|
|
98
|
+
export function installVueGrab(targetWindow, options = {}) {
|
|
99
|
+
const existing = targetWindow.__VUE_GRAB__;
|
|
100
|
+
if (existing)
|
|
101
|
+
return existing;
|
|
102
|
+
const api = createVueGrabAPI(targetWindow, options);
|
|
103
|
+
targetWindow.__VUE_GRAB__ = api;
|
|
104
|
+
return api;
|
|
105
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export function identifyComponent(el) {
|
|
2
|
+
let curr = el;
|
|
3
|
+
while (curr) {
|
|
4
|
+
// Vue 3 stores the internal component instance on the DOM element
|
|
5
|
+
// under specific keys depending on the version/environment.
|
|
6
|
+
const currAny = curr;
|
|
7
|
+
const instance = currAny.__vueParentComponent ||
|
|
8
|
+
currAny.__vnode?.component ||
|
|
9
|
+
currAny.__vnode?.ctx ||
|
|
10
|
+
currAny.__vue__;
|
|
11
|
+
if (instance)
|
|
12
|
+
return instance;
|
|
13
|
+
curr = curr.parentElement;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function getElementLoc(el) {
|
|
18
|
+
if (!el || typeof el.getAttribute !== 'function')
|
|
19
|
+
return null;
|
|
20
|
+
const locAttr = el.getAttribute('data-vue-grab-loc');
|
|
21
|
+
if (!locAttr)
|
|
22
|
+
return null;
|
|
23
|
+
const [lineRaw, columnRaw] = locAttr.split(':');
|
|
24
|
+
const line = Number(lineRaw);
|
|
25
|
+
const column = Number(columnRaw);
|
|
26
|
+
if (!Number.isFinite(line) || !Number.isFinite(column))
|
|
27
|
+
return null;
|
|
28
|
+
return { line, column };
|
|
29
|
+
}
|
|
30
|
+
export function extractMetadata(instance, el) {
|
|
31
|
+
if (!instance && !el)
|
|
32
|
+
return null;
|
|
33
|
+
const resolveType = (start) => {
|
|
34
|
+
let curr = start;
|
|
35
|
+
while (curr) {
|
|
36
|
+
const t = curr.type || curr.$options || curr.type?.__vccOpts || {};
|
|
37
|
+
const file = t.__file || t.__vccOpts?.__file;
|
|
38
|
+
if (file) {
|
|
39
|
+
return { type: t, instance: curr };
|
|
40
|
+
}
|
|
41
|
+
curr = curr.parent;
|
|
42
|
+
}
|
|
43
|
+
return { type: start.type || start.$options || start.type?.__vccOpts || {}, instance: start };
|
|
44
|
+
};
|
|
45
|
+
const resolved = instance ? resolveType(instance) : { type: {}, instance: null };
|
|
46
|
+
const type = resolved.type || {};
|
|
47
|
+
const props = instance?.props || instance?.$props || {};
|
|
48
|
+
const data = (instance?.data && Object.keys(instance.data).length > 0
|
|
49
|
+
? instance.data
|
|
50
|
+
: instance?.setupState) ||
|
|
51
|
+
instance?.$data ||
|
|
52
|
+
{};
|
|
53
|
+
const vnode = instance?.vnode || instance?.$vnode;
|
|
54
|
+
const domLoc = getElementLoc(el);
|
|
55
|
+
const loc = domLoc ||
|
|
56
|
+
vnode?.loc?.start ||
|
|
57
|
+
resolved.instance?.vnode?.loc?.start ||
|
|
58
|
+
resolved.instance?.parent?.vnode?.loc?.start;
|
|
59
|
+
const metadata = instance
|
|
60
|
+
? {
|
|
61
|
+
name: type.name || type.__name || type.__vccOpts?.name || 'AnonymousComponent',
|
|
62
|
+
file: type.__file || type.__vccOpts?.__file || 'unknown',
|
|
63
|
+
props,
|
|
64
|
+
data
|
|
65
|
+
}
|
|
66
|
+
: {
|
|
67
|
+
name: el?.tagName ? `<${el.tagName.toLowerCase()}>` : 'unknown',
|
|
68
|
+
file: 'unknown',
|
|
69
|
+
props: {},
|
|
70
|
+
data: {},
|
|
71
|
+
element: el || null
|
|
72
|
+
};
|
|
73
|
+
if (typeof loc?.line === 'number') {
|
|
74
|
+
metadata.line = loc.line;
|
|
75
|
+
}
|
|
76
|
+
else if (typeof type.__line === 'number') {
|
|
77
|
+
metadata.line = type.__line;
|
|
78
|
+
}
|
|
79
|
+
else if (typeof type.line === 'number') {
|
|
80
|
+
metadata.line = type.line;
|
|
81
|
+
}
|
|
82
|
+
else if (typeof type.__vccOpts?.__line === 'number') {
|
|
83
|
+
metadata.line = type.__vccOpts.__line;
|
|
84
|
+
}
|
|
85
|
+
if (typeof loc?.column === 'number') {
|
|
86
|
+
metadata.column = loc.column;
|
|
87
|
+
}
|
|
88
|
+
else if (typeof type.__column === 'number') {
|
|
89
|
+
metadata.column = type.__column;
|
|
90
|
+
}
|
|
91
|
+
else if (typeof type.column === 'number') {
|
|
92
|
+
metadata.column = type.column;
|
|
93
|
+
}
|
|
94
|
+
else if (typeof type.__vccOpts?.__column === 'number') {
|
|
95
|
+
metadata.column = type.__vccOpts.__column;
|
|
96
|
+
}
|
|
97
|
+
if (vnode) {
|
|
98
|
+
metadata.vnode = vnode;
|
|
99
|
+
}
|
|
100
|
+
return metadata;
|
|
101
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type OverlayController = {
|
|
2
|
+
start: () => void;
|
|
3
|
+
stop: () => void;
|
|
4
|
+
isActive: () => boolean;
|
|
5
|
+
highlight: (el: HTMLElement | null) => void;
|
|
6
|
+
clear: () => void;
|
|
7
|
+
setStyle: (style: OverlayStyle) => void;
|
|
8
|
+
setDomFileResolver: (resolver: OverlayOptions['domFileResolver']) => void;
|
|
9
|
+
};
|
|
10
|
+
export type OverlayStyle = Record<string, string>;
|
|
11
|
+
export type OverlayOptions = {
|
|
12
|
+
overlayStyle?: OverlayStyle;
|
|
13
|
+
onCopy?: (payload: string) => void;
|
|
14
|
+
onAfterCopy?: () => void;
|
|
15
|
+
copyOnClick?: boolean;
|
|
16
|
+
rootDir?: string;
|
|
17
|
+
domFileResolver?: (el: HTMLElement) => {
|
|
18
|
+
file?: string;
|
|
19
|
+
line?: number;
|
|
20
|
+
column?: number;
|
|
21
|
+
} | null;
|
|
22
|
+
};
|
|
23
|
+
export declare function createOverlayController(targetWindow: Window, options?: OverlayOptions): OverlayController;
|
|
24
|
+
export {};
|