@akccakcctw/vue-grab 1.0.0 → 1.4.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.
Files changed (43) hide show
  1. package/README.md +25 -52
  2. package/dist/core/api.d.ts +28 -0
  3. package/dist/core/api.js +105 -0
  4. package/dist/core/identifier.d.ts +2 -0
  5. package/dist/core/identifier.js +101 -0
  6. package/dist/core/overlay.d.ts +25 -0
  7. package/dist/core/overlay.js +660 -0
  8. package/dist/core/widget.d.ts +10 -0
  9. package/dist/core/widget.js +251 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.js +7 -0
  12. package/dist/node/agent-task.d.ts +7 -0
  13. package/dist/node/agent-task.js +48 -0
  14. package/dist/nuxt/module.d.ts +8 -0
  15. package/dist/nuxt/module.js +40 -0
  16. package/dist/nuxt/runtime/plugin.d.ts +2 -0
  17. package/dist/nuxt/runtime/plugin.js +20 -0
  18. package/dist/plugin.d.ts +11 -0
  19. package/dist/plugin.js +55 -0
  20. package/dist/vite.d.ts +8 -0
  21. package/dist/vite.js +219 -0
  22. package/package.json +33 -8
  23. package/.github/release-please-config.json +0 -9
  24. package/.github/release-please-manifest.json +0 -3
  25. package/.github/workflows/release.yml +0 -37
  26. package/AGENTS.md +0 -75
  27. package/akccakcctw-vue-grab-1.0.0.tgz +0 -0
  28. package/docs/SDD.md +0 -188
  29. package/src/__tests__/plugin.spec.ts +0 -60
  30. package/src/core/__tests__/api.spec.ts +0 -178
  31. package/src/core/__tests__/identifier.spec.ts +0 -126
  32. package/src/core/__tests__/overlay.spec.ts +0 -431
  33. package/src/core/__tests__/widget.spec.ts +0 -57
  34. package/src/core/api.ts +0 -144
  35. package/src/core/identifier.ts +0 -89
  36. package/src/core/overlay.ts +0 -348
  37. package/src/core/widget.ts +0 -289
  38. package/src/index.ts +0 -8
  39. package/src/nuxt/module.ts +0 -102
  40. package/src/nuxt/runtime/plugin.ts +0 -13
  41. package/src/plugin.ts +0 -48
  42. package/tsconfig.json +0 -44
  43. package/vitest.config.ts +0 -9
package/README.md CHANGED
@@ -1,24 +1,27 @@
1
- # vue-grab
1
+ # @akccakcctw/vue-grab
2
2
 
3
3
  Developer-only bridge for inspecting Vue/Nuxt component context from the DOM.
4
4
 
5
+ [![npm](https://img.shields.io/npm/v/@akccakcctw/vue-grab)](https://www.npmjs.com/package/@akccakcctw/vue-grab)
6
+ NPM package: https://www.npmjs.com/package/@akccakcctw/vue-grab
7
+
5
8
  ## Install
6
9
 
7
10
  ```bash
8
- pnpm add -D vue-grab
11
+ pnpm add -D @akccakcctw/vue-grab
9
12
  ```
10
13
 
11
14
  ## Vue Usage
12
15
 
13
16
  ```ts
14
17
  import { createApp } from 'vue'
15
- import VueGrab from 'vue-grab'
18
+ import { createVueGrabPlugin } from '@akccakcctw/vue-grab'
16
19
 
17
20
  const app = createApp(App)
18
21
 
19
22
  // Use import.meta.env.DEV for Vite, or process.env.NODE_ENV === 'development' for Webpack
20
23
  if (import.meta.env.DEV) {
21
- app.use(VueGrab, {
24
+ app.use(createVueGrabPlugin({
22
25
  overlayStyle: {
23
26
  border: '2px dashed #111'
24
27
  },
@@ -26,7 +29,7 @@ if (import.meta.env.DEV) {
26
29
  console.log('vue-grab payload', payload)
27
30
  },
28
31
  copyOnClick: true
29
- })
32
+ }))
30
33
  }
31
34
 
32
35
  app.mount('#app')
@@ -36,7 +39,7 @@ app.mount('#app')
36
39
 
37
40
  ```ts
38
41
  export default defineNuxtConfig({
39
- modules: ['vue-grab'],
42
+ modules: ['@akccakcctw/vue-grab'],
40
43
  vueGrab: {
41
44
  enabled: true,
42
45
  overlayStyle: {
@@ -47,6 +50,21 @@ export default defineNuxtConfig({
47
50
  })
48
51
  ```
49
52
 
53
+ ## Vite Line/Column Support
54
+
55
+ Add the Vite plugin to inject `__file`, `__line`, and `__column` metadata into SFCs.
56
+ It also annotates template DOM nodes with their original line/column so element grabs map to the template line numbers.
57
+
58
+ ```ts
59
+ import { defineConfig } from 'vite'
60
+ import vue from '@vitejs/plugin-vue'
61
+ import { createVueGrabVitePlugin } from '@akccakcctw/vue-grab/vite'
62
+
63
+ export default defineConfig({
64
+ plugins: [vue(), createVueGrabVitePlugin()]
65
+ })
66
+ ```
67
+
50
68
  ## Browser API
51
69
 
52
70
  When active, `window.__VUE_GRAB__` exposes:
@@ -68,49 +86,4 @@ const info = window.__VUE_GRAB__.grabFromSelector('.my-button');
68
86
  console.log(JSON.stringify(info));
69
87
  ```
70
88
 
71
- ## Development
72
-
73
- For maintainers of this package (local dev + manual testing):
74
-
75
- ```bash
76
- pnpm install
77
- pnpm test
78
- ```
79
-
80
- To test in a real app, link this package into a separate Vue/Nuxt project:
81
-
82
- ```bash
83
- # In this repo
84
- pnpm link --global
85
-
86
- # In your app repo
87
- pnpm link --global vue-grab
88
- ```
89
-
90
- Note: `pnpm link --global vue-grab` does not update your app's `package.json` by default.
91
- If you want it recorded in dependencies, use one of the following in your app repo:
92
-
93
- ```bash
94
- pnpm add -D link:vue-grab
95
- # or
96
- pnpm add -D link:/Users/rex.tsou/vbox/vue-grab
97
- ```
98
-
99
- Then run your app and verify `window.__VUE_GRAB__` in the browser console.
100
-
101
- Cleanup when finished:
102
-
103
- ```bash
104
- # In your app repo
105
- pnpm unlink --global vue-grab
106
- ```
107
-
108
- Manual verification checklist:
109
- - Hover highlights appear when `activate()` is called.
110
- - Clicking copies metadata (or triggers `onCopy` if configured).
111
- - `grabFromSelector` returns component info for a known element.
112
-
113
- ## Acknowledgment
114
-
115
- Special thanks to [react-grab](https://www.react-grab.com/) ([GitHub](https://github.com/aidenybai/react-grab)). This project was inspired by and references the excellent work done by the `react-grab` team. `vue-grab` aims to bring a similar developer experience to the Vue and Nuxt ecosystem.
116
-
89
+ For the full documentation, see https://github.com/akccakcctw/vue-grab.
@@ -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;
@@ -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,2 @@
1
+ export declare function identifyComponent(el: HTMLElement | null): any;
2
+ export declare function extractMetadata(instance: any, el?: HTMLElement | null): Record<string, any> | null;
@@ -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,25 @@
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
+ onAgentTask?: (task: any) => void;
16
+ copyOnClick?: boolean;
17
+ rootDir?: string;
18
+ domFileResolver?: (el: HTMLElement) => {
19
+ file?: string;
20
+ line?: number;
21
+ column?: number;
22
+ } | null;
23
+ };
24
+ export declare function createOverlayController(targetWindow: Window, options?: OverlayOptions): OverlayController;
25
+ export {};