@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.
Files changed (41) hide show
  1. package/dist/core/api.d.ts +28 -0
  2. package/dist/core/api.js +105 -0
  3. package/dist/core/identifier.d.ts +2 -0
  4. package/dist/core/identifier.js +101 -0
  5. package/dist/core/overlay.d.ts +24 -0
  6. package/dist/core/overlay.js +509 -0
  7. package/dist/core/widget.d.ts +10 -0
  8. package/dist/core/widget.js +251 -0
  9. package/dist/index.d.ts +10 -0
  10. package/dist/index.js +7 -0
  11. package/dist/nuxt/module.d.ts +8 -0
  12. package/dist/nuxt/module.js +40 -0
  13. package/dist/nuxt/runtime/plugin.d.ts +2 -0
  14. package/dist/nuxt/runtime/plugin.js +14 -0
  15. package/dist/plugin.d.ts +11 -0
  16. package/dist/plugin.js +47 -0
  17. package/dist/vite.d.ts +8 -0
  18. package/dist/vite.js +198 -0
  19. package/package.json +33 -8
  20. package/.github/release-please-config.json +0 -9
  21. package/.github/release-please-manifest.json +0 -3
  22. package/.github/workflows/release.yml +0 -37
  23. package/AGENTS.md +0 -75
  24. package/README.md +0 -116
  25. package/akccakcctw-vue-grab-1.0.0.tgz +0 -0
  26. package/docs/SDD.md +0 -188
  27. package/src/__tests__/plugin.spec.ts +0 -60
  28. package/src/core/__tests__/api.spec.ts +0 -178
  29. package/src/core/__tests__/identifier.spec.ts +0 -126
  30. package/src/core/__tests__/overlay.spec.ts +0 -431
  31. package/src/core/__tests__/widget.spec.ts +0 -57
  32. package/src/core/api.ts +0 -144
  33. package/src/core/identifier.ts +0 -89
  34. package/src/core/overlay.ts +0 -348
  35. package/src/core/widget.ts +0 -289
  36. package/src/index.ts +0 -8
  37. package/src/nuxt/module.ts +0 -102
  38. package/src/nuxt/runtime/plugin.ts +0 -13
  39. package/src/plugin.ts +0 -48
  40. package/tsconfig.json +0 -44
  41. package/vitest.config.ts +0 -9
@@ -1,289 +0,0 @@
1
- type ToggleWidgetOptions = {
2
- onToggle: (nextActive: boolean) => void;
3
- };
4
-
5
- type ToggleWidgetController = {
6
- mount: () => void;
7
- unmount: () => void;
8
- setActive: (active: boolean) => void;
9
- };
10
-
11
- type DragState = {
12
- dragging: boolean;
13
- offsetX: number;
14
- offsetY: number;
15
- moved: boolean;
16
- };
17
-
18
- const CURSOR_ICON = `<svg data-v-6fdbd1c9="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="height: 1.2em; width: 1.2em; pointer-events: none;"><g data-v-6fdbd1c9="" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle data-v-6fdbd1c9="" cx="12" cy="12" r=".5" fill="currentColor"></circle><path data-v-6fdbd1c9="" d="M5 12a7 7 0 1 0 14 0a7 7 0 1 0-14 0m7-9v2m-9 7h2m7 7v2m7-9h2"></path></g></svg>`;
19
- const CHEVRON_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" style="transform: rotate(180deg); pointer-events: none;"><path d="m18 15-6-6-6 6"></path></svg>`;
20
-
21
- function createToolbar(targetWindow: Window) {
22
- const doc = targetWindow.document;
23
- const container = doc.createElement('div');
24
-
25
- // Outer Container Styles
26
- Object.assign(container.style, {
27
- position: 'fixed',
28
- left: '16px',
29
- top: '16px',
30
- zIndex: '2147483647',
31
- fontFamily: 'sans-serif',
32
- fontSize: '13px',
33
- userSelect: 'none',
34
- cursor: 'grab',
35
- filter: 'drop-shadow(0px 0px 4px rgba(81, 81, 81, 0.5))',
36
- transition: 'opacity 300ms ease-out, padding 0.2s ease',
37
- opacity: '1',
38
- });
39
- container.setAttribute('data-vue-grab-toolbar', '');
40
- container.setAttribute('data-vue-grab-ignore-events', '');
41
-
42
- const inner = doc.createElement('div');
43
- Object.assign(inner.style, {
44
- display: 'flex',
45
- alignItems: 'center',
46
- justifyContent: 'center',
47
- borderRadius: '4px',
48
- backgroundColor: 'white',
49
- gap: '6px',
50
- padding: '6px 8px',
51
- transition: 'gap 0.2s ease, padding 0.2s ease',
52
- });
53
-
54
- // Toggle Button Wrapper
55
- const toggleWrapper = doc.createElement('div');
56
- Object.assign(toggleWrapper.style, {
57
- display: 'flex',
58
- alignItems: 'center',
59
- gap: '6px',
60
- overflow: 'hidden',
61
- maxWidth: '200px',
62
- transition: 'max-width 0.2s ease, opacity 0.2s ease'
63
- });
64
-
65
- // Toggle Button
66
- const toggleBtn = doc.createElement('button');
67
- toggleBtn.setAttribute('data-vue-grab-toggle', '');
68
- Object.assign(toggleBtn.style, {
69
- display: 'flex',
70
- alignItems: 'center',
71
- justifyContent: 'center',
72
- cursor: 'pointer',
73
- border: 'none',
74
- background: 'transparent',
75
- padding: '0',
76
- transition: 'transform 0.1s',
77
- color: 'rgba(0, 0, 0, 0.7)'
78
- });
79
- toggleBtn.innerHTML = CURSOR_ICON;
80
- toggleBtn.onmouseenter = () => toggleBtn.style.transform = 'scale(1.05)';
81
- toggleBtn.onmouseleave = () => toggleBtn.style.transform = 'scale(1)';
82
-
83
- toggleWrapper.appendChild(toggleBtn);
84
-
85
- // Collapse Button
86
- const collapseBtn = doc.createElement('button');
87
- collapseBtn.setAttribute('data-vue-grab-collapse', '');
88
- Object.assign(collapseBtn.style, {
89
- display: 'flex',
90
- alignItems: 'center',
91
- justifyContent: 'center',
92
- cursor: 'pointer',
93
- border: 'none',
94
- background: 'transparent',
95
- padding: '0',
96
- transition: 'transform 0.1s',
97
- color: '#B3B3B3'
98
- });
99
- collapseBtn.innerHTML = CHEVRON_ICON;
100
- collapseBtn.onmouseenter = () => collapseBtn.style.transform = 'scale(1.05)';
101
- collapseBtn.onmouseleave = () => collapseBtn.style.transform = 'scale(1)';
102
-
103
- inner.appendChild(toggleWrapper);
104
- inner.appendChild(collapseBtn);
105
- container.appendChild(inner);
106
-
107
- return { container, toggleBtn, collapseBtn, toggleWrapper };
108
- }
109
-
110
- function setButtonState(toggleBtn: HTMLElement, active: boolean) {
111
- // Use color to indicate state: Blue for active, Gray for inactive
112
- toggleBtn.style.color = active ? '#3b82f6' : 'rgba(0, 0, 0, 0.7)';
113
- }
114
-
115
- export function createToggleWidget(
116
- targetWindow: Window,
117
- options: ToggleWidgetOptions
118
- ): ToggleWidgetController {
119
- let elements:
120
- | {
121
- container: HTMLDivElement;
122
- toggleBtn: HTMLButtonElement;
123
- collapseBtn: HTMLButtonElement;
124
- toggleWrapper: HTMLDivElement;
125
- }
126
- | null = null;
127
- let mounted = false;
128
- let isActive = false;
129
- let isCollapsed = false;
130
- let lastPosition = {
131
- left: '',
132
- top: '',
133
- right: '',
134
- bottom: ''
135
- };
136
- const defaultInnerGap = '6px';
137
- const defaultInnerPadding = '6px 8px';
138
- const dragState: DragState = {
139
- dragging: false,
140
- offsetX: 0,
141
- offsetY: 0,
142
- moved: false
143
- };
144
-
145
- const startDrag = (event: MouseEvent) => {
146
- if (!elements) return;
147
- // Don't drag if clicking buttons directly might be better handled by stopPropagation,
148
- // but here we allow dragging from anywhere on the container.
149
- // However, if we click a button, we might want to prevent drag start or ensure click works?
150
- // Usually standard behavior: mousedown + mouseup without move = click. mousedown + move = drag.
151
-
152
- dragState.dragging = true;
153
- dragState.moved = false;
154
- const rect = elements.container.getBoundingClientRect();
155
- dragState.offsetX = event.clientX - rect.left;
156
- dragState.offsetY = event.clientY - rect.top;
157
-
158
- // Switch to explicit coords for dragging
159
- elements.container.style.right = 'auto';
160
- elements.container.style.bottom = 'auto';
161
- elements.container.style.left = `${rect.left}px`;
162
- elements.container.style.top = `${rect.top}px`;
163
- elements.container.style.cursor = 'grabbing';
164
- };
165
-
166
- const onDrag = (event: MouseEvent) => {
167
- if (!elements || !dragState.dragging) return;
168
- dragState.moved = true;
169
- const nextLeft = event.clientX - dragState.offsetX;
170
- const nextTop = event.clientY - dragState.offsetY;
171
- const maxLeft = targetWindow.innerWidth - elements.container.offsetWidth;
172
- const maxTop = targetWindow.innerHeight - elements.container.offsetHeight;
173
- const clampedLeft = Math.max(0, Math.min(maxLeft, nextLeft));
174
- const clampedTop = Math.max(0, Math.min(maxTop, nextTop));
175
- elements.container.style.left = `${clampedLeft}px`;
176
- elements.container.style.top = `${clampedTop}px`;
177
- };
178
-
179
- const endDrag = () => {
180
- if (!elements) return;
181
- dragState.dragging = false;
182
- elements.container.style.cursor = 'grab';
183
- };
184
-
185
- const toggleCollapse = () => {
186
- if (!elements) return;
187
- const { container, toggleWrapper, collapseBtn } = elements;
188
- const inner = container.firstElementChild as HTMLDivElement | null;
189
- const svg = collapseBtn.querySelector('svg') as SVGElement | null;
190
-
191
- if (!isCollapsed) {
192
- const rect = container.getBoundingClientRect();
193
- lastPosition = {
194
- left: container.style.left,
195
- top: container.style.top,
196
- right: container.style.right,
197
- bottom: container.style.bottom
198
- };
199
- const stickLeft = rect.left + rect.width / 2 < targetWindow.innerWidth / 2;
200
- container.style.top = `${rect.top}px`;
201
- if (stickLeft) {
202
- container.style.left = '0px';
203
- container.style.right = 'auto';
204
- } else {
205
- container.style.left = 'auto';
206
- container.style.right = '0px';
207
- }
208
- container.style.bottom = 'auto';
209
- container.style.transform = 'scale(0.8)';
210
- container.style.padding = '0';
211
- toggleWrapper.style.maxWidth = '0px';
212
- toggleWrapper.style.opacity = '0';
213
- toggleWrapper.style.pointerEvents = 'none';
214
- if (inner) {
215
- inner.style.gap = '0';
216
- inner.style.padding = '6px';
217
- }
218
- if (svg) svg.style.transform = 'rotate(0deg)';
219
- isCollapsed = true;
220
- } else {
221
- container.style.left = lastPosition.left;
222
- container.style.top = lastPosition.top;
223
- container.style.right = lastPosition.right;
224
- container.style.bottom = lastPosition.bottom;
225
- container.style.transform = 'scale(1)';
226
- container.style.padding = '';
227
- toggleWrapper.style.maxWidth = '200px';
228
- toggleWrapper.style.opacity = '1';
229
- toggleWrapper.style.pointerEvents = 'auto';
230
- if (inner) {
231
- inner.style.gap = defaultInnerGap;
232
- inner.style.padding = defaultInnerPadding;
233
- }
234
- if (svg) svg.style.transform = 'rotate(180deg)';
235
- isCollapsed = false;
236
- }
237
- };
238
-
239
- return {
240
- mount() {
241
- if (mounted) return;
242
- mounted = true;
243
- elements = createToolbar(targetWindow);
244
- if (!elements) return;
245
-
246
- setButtonState(elements.toggleBtn, isActive);
247
-
248
- // Drag listeners on the container
249
- elements.container.addEventListener('mousedown', startDrag);
250
-
251
- // Toggle logic
252
- elements.toggleBtn.addEventListener('click', (event) => {
253
- event.preventDefault();
254
- event.stopPropagation(); // Prevent affecting container?
255
- if (dragState.moved) return; // Prevent toggle if it was a drag
256
-
257
- isActive = !isActive;
258
- setButtonState(elements!.toggleBtn, isActive);
259
- options.onToggle(isActive);
260
- });
261
-
262
- // Collapse logic (Placeholder)
263
- elements.collapseBtn.addEventListener('click', (event) => {
264
- event.preventDefault();
265
- event.stopPropagation();
266
- if (dragState.moved) return;
267
- toggleCollapse();
268
- });
269
-
270
- targetWindow.addEventListener('mousemove', onDrag);
271
- targetWindow.addEventListener('mouseup', endDrag);
272
- targetWindow.document.body.appendChild(elements.container);
273
- },
274
- unmount() {
275
- if (!mounted || !elements) return;
276
- mounted = false;
277
- elements.container.removeEventListener('mousedown', startDrag);
278
- elements.container.remove();
279
- elements = null;
280
- targetWindow.removeEventListener('mousemove', onDrag);
281
- targetWindow.removeEventListener('mouseup', endDrag);
282
- },
283
- setActive(active: boolean) {
284
- isActive = active;
285
- if (!elements) return;
286
- setButtonState(elements.toggleBtn, active);
287
- }
288
- };
289
- }
package/src/index.ts DELETED
@@ -1,8 +0,0 @@
1
- export { createVueGrabAPI, installVueGrab } from './core/api';
2
- export type { VueGrabOptions } from './core/api';
3
- export type { OverlayOptions, OverlayStyle } from './core/overlay';
4
- export { createToggleWidget } from './core/widget';
5
- export { identifyComponent, extractMetadata } from './core/identifier';
6
- export { createOverlayController } from './core/overlay';
7
- export { createVueGrabPlugin } from './plugin';
8
- export { default } from './plugin';
@@ -1,102 +0,0 @@
1
- import { addPlugin, createResolver, defineNuxtModule, addVitePlugin } from '@nuxt/kit';
2
-
3
- export type VueGrabNuxtModuleOptions = {
4
- enabled?: boolean;
5
- overlayStyle?: Record<string, string>;
6
- copyOnClick?: boolean;
7
- rootDir?: string;
8
- };
9
-
10
- export default defineNuxtModule<VueGrabNuxtModuleOptions>({
11
- meta: {
12
- name: 'vue-grab',
13
- configKey: 'vueGrab'
14
- },
15
- defaults: {
16
- enabled: true
17
- },
18
- setup(options, nuxt) {
19
- if (options.enabled === false) return;
20
- const shouldEnable = nuxt.options.dev || options.enabled === true;
21
- if (!shouldEnable) return;
22
-
23
- const publicConfig = (nuxt.options.runtimeConfig.public ||= {});
24
- const { enabled, overlayStyle, copyOnClick, rootDir } = options;
25
- publicConfig.vueGrab = {
26
- ...(publicConfig.vueGrab as Record<string, any> | undefined),
27
- enabled,
28
- overlayStyle,
29
- copyOnClick,
30
- rootDir: rootDir || nuxt.options.rootDir
31
- };
32
-
33
- nuxt.options.build.transpile = nuxt.options.build.transpile || [];
34
- if (!nuxt.options.build.transpile.includes('vue-grab')) {
35
- nuxt.options.build.transpile.push('vue-grab');
36
- }
37
-
38
- const resolver = createResolver(import.meta.url);
39
- addPlugin({
40
- src: resolver.resolve('./runtime/plugin'),
41
- mode: 'client'
42
- });
43
-
44
- addVitePlugin({
45
- name: 'vite-plugin-vue-grab-injector',
46
- enforce: 'post',
47
- transform(code, id) {
48
- if (!id.match(/\.vue($|\?)/) || id.includes('node_modules')) return;
49
- const [filename] = id.split('?');
50
- if (!filename) return;
51
-
52
- const file = filename.replace(nuxt.options.rootDir, '');
53
-
54
- const getLineCol = (index: number) => {
55
- const pre = code.slice(0, index);
56
- const lines = pre.split('\n');
57
- const line = lines.length;
58
- const column = lines[lines.length - 1].length + 1;
59
- return { line, column };
60
- };
61
-
62
- // 1. Handle "export default _sfc_main" (common in script setup)
63
- const matchVar = code.match(/export default\s+([a-zA-Z0-9_$]+)/);
64
- if (matchVar && matchVar.index !== undefined) {
65
- const name = matchVar[1];
66
- // For variable export, the definition is usually earlier.
67
- // We can't easily find the definition line without parsing.
68
- // But we can fallback to 1, or try to find "const name ="?
69
- // For script setup, the variable is defined near the top or bottom.
70
- // Let's use 1 as fallback for variable export, or try to find definition.
71
- // Actually, let's just stick to file path for now for variable, or use 1.
72
- // Nuxt DevTools often uses 1 for SFCs unless it parses the template.
73
- const inject = `\n${name}.__file = ${JSON.stringify(file)};\n${name}.__line = 1;\n${name}.__column = 1;\n`;
74
- return {
75
- code: code.replace(matchVar[0], inject + matchVar[0]),
76
- map: null
77
- };
78
- }
79
-
80
- // 2. Handle "export default { ... }"
81
- const matchObj = code.match(/export default\s*\{/);
82
- if (matchObj && matchObj.index !== undefined) {
83
- const { line, column } = getLineCol(matchObj.index);
84
- return {
85
- code: code.replace(/export default\s*\{/, `export default { __file: ${JSON.stringify(file)}, __line: ${line}, __column: ${column},`),
86
- map: null
87
- };
88
- }
89
-
90
- // 3. Handle "export default defineComponent({ ... })"
91
- const matchDef = code.match(/export default\s+defineComponent\s*\(\s*\{/);
92
- if (matchDef && matchDef.index !== undefined) {
93
- const { line, column } = getLineCol(matchDef.index);
94
- return {
95
- code: code.replace(/export default\s+defineComponent\s*\(\s*\{/, `export default defineComponent({ __file: ${JSON.stringify(file)}, __line: ${line}, __column: ${column},`),
96
- map: null
97
- };
98
- }
99
- }
100
- });
101
- }
102
- });
@@ -1,13 +0,0 @@
1
- import { defineNuxtPlugin, useRuntimeConfig } from '#app';
2
- import { installVueGrab } from '../../core/api';
3
-
4
- export default defineNuxtPlugin(() => {
5
- const config = useRuntimeConfig().public?.vueGrab ?? {};
6
- if (config.enabled === false) return;
7
- if (typeof window === 'undefined') return;
8
- installVueGrab(window, {
9
- overlayStyle: config.overlayStyle,
10
- copyOnClick: config.copyOnClick,
11
- rootDir: config.rootDir
12
- });
13
- });
package/src/plugin.ts DELETED
@@ -1,48 +0,0 @@
1
- import { installVueGrab } from './core/api';
2
- import type { VueGrabOptions } from './core/api';
3
-
4
- export type VueGrabPluginOptions = VueGrabOptions & {
5
- enabled?: boolean;
6
- };
7
-
8
- function isDevEnvironment() {
9
- if (typeof import.meta !== 'undefined' && (import.meta as any).env) {
10
- return Boolean((import.meta as any).env.DEV);
11
- }
12
-
13
- try {
14
- const globalScope =
15
- typeof globalThis !== 'undefined' ? globalThis :
16
- typeof window !== 'undefined' ? window :
17
- typeof self !== 'undefined' ? self :
18
- typeof global !== 'undefined' ? global : {};
19
-
20
- const proc = (globalScope as any).process;
21
- if (proc && proc.env) {
22
- return proc.env.NODE_ENV !== 'production';
23
- }
24
- } catch {
25
- // ignore
26
- }
27
- return false;
28
- }
29
-
30
- export function createVueGrabPlugin(options: VueGrabPluginOptions = {}) {
31
- return {
32
- install() {
33
- const enabled =
34
- typeof options.enabled === 'boolean' ? options.enabled : isDevEnvironment();
35
- if (enabled && typeof window !== 'undefined') {
36
- installVueGrab(window, {
37
- overlayStyle: options.overlayStyle,
38
- onCopy: options.onCopy,
39
- copyOnClick: options.copyOnClick,
40
- rootDir: options.rootDir,
41
- domFileResolver: options.domFileResolver
42
- });
43
- }
44
- }
45
- };
46
- }
47
-
48
- export default createVueGrabPlugin();
package/tsconfig.json DELETED
@@ -1,44 +0,0 @@
1
- {
2
- // Visit https://aka.ms/tsconfig to read more about this file
3
- "compilerOptions": {
4
- // File Layout
5
- // "rootDir": "./src",
6
- // "outDir": "./dist",
7
-
8
- // Environment Settings
9
- // See also https://aka.ms/tsconfig/module
10
- "module": "nodenext",
11
- "target": "esnext",
12
- "types": [],
13
- // For nodejs:
14
- // "lib": ["esnext"],
15
- // "types": ["node"],
16
- // and npm install -D @types/node
17
-
18
- // Other Outputs
19
- "sourceMap": true,
20
- "declaration": true,
21
- "declarationMap": true,
22
-
23
- // Stricter Typechecking Options
24
- "noUncheckedIndexedAccess": true,
25
- "exactOptionalPropertyTypes": true,
26
-
27
- // Style Options
28
- // "noImplicitReturns": true,
29
- // "noImplicitOverride": true,
30
- // "noUnusedLocals": true,
31
- // "noUnusedParameters": true,
32
- // "noFallthroughCasesInSwitch": true,
33
- // "noPropertyAccessFromIndexSignature": true,
34
-
35
- // Recommended Options
36
- "strict": true,
37
- "jsx": "react-jsx",
38
- "verbatimModuleSyntax": true,
39
- "isolatedModules": true,
40
- "noUncheckedSideEffectImports": true,
41
- "moduleDetection": "force",
42
- "skipLibCheck": true,
43
- }
44
- }
package/vitest.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineConfig } from 'vitest/config'
2
- import vue from '@vitejs/plugin-vue'
3
-
4
- export default defineConfig({
5
- plugins: [vue() as any],
6
- test: {
7
- environment: 'jsdom',
8
- },
9
- })