@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,126 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { mount } from '@vue/test-utils'
3
- import { defineComponent, h } from 'vue'
4
- import { identifyComponent, extractMetadata } from '../identifier'
5
-
6
- const TestComponent = defineComponent({
7
- name: 'TestComponent',
8
- __file: '/abs/path/to/TestComponent.vue',
9
- props: {
10
- label: {
11
- type: String,
12
- default: 'Default'
13
- }
14
- },
15
- data() {
16
- return {
17
- count: 0
18
- }
19
- },
20
- setup() {
21
- return () => h('div', { class: 'target' }, 'Hello')
22
- }
23
- })
24
-
25
- describe('Component Identifier', () => {
26
- it('should identify a component from a DOM element', () => {
27
- const wrapper = mount(TestComponent)
28
- const el = wrapper.find('.target').element as HTMLElement
29
-
30
- const instance = identifyComponent(el)
31
- expect(instance).toBeDefined()
32
- })
33
-
34
- it('should extract metadata from a component instance', () => {
35
- const wrapper = mount(TestComponent, {
36
- props: {
37
- label: 'Click Me'
38
- }
39
- })
40
- const el = wrapper.find('.target').element as HTMLElement
41
-
42
- const instance = identifyComponent(el)
43
- const metadata = extractMetadata(instance)
44
-
45
- expect(metadata).toMatchObject({
46
- name: 'TestComponent',
47
- file: '/abs/path/to/TestComponent.vue',
48
- props: {
49
- label: 'Click Me'
50
- },
51
- data: {
52
- count: 0
53
- }
54
- })
55
- })
56
-
57
- it('includes vnode and location data when available', () => {
58
- const instance = {
59
- type: {
60
- name: 'MockComponent',
61
- __file: '/abs/path/to/MockComponent.vue'
62
- },
63
- vnode: {
64
- loc: {
65
- start: {
66
- line: 12,
67
- column: 8
68
- }
69
- }
70
- }
71
- }
72
-
73
- const metadata = extractMetadata(instance)
74
-
75
- expect(metadata).toMatchObject({
76
- name: 'MockComponent',
77
- file: '/abs/path/to/MockComponent.vue',
78
- line: 12,
79
- column: 8,
80
- vnode: instance.vnode
81
- })
82
- })
83
-
84
- it('falls back to parent metadata when file is missing', () => {
85
- const parent = {
86
- type: {
87
- name: 'ParentComponent',
88
- __file: '/abs/path/to/ParentComponent.vue',
89
- __line: 5,
90
- __column: 2
91
- },
92
- vnode: {
93
- loc: {
94
- start: {
95
- line: 5,
96
- column: 2
97
- }
98
- }
99
- }
100
- }
101
- const child = {
102
- type: {
103
- name: 'ChildComponent'
104
- },
105
- parent
106
- }
107
-
108
- const metadata = extractMetadata(child)
109
-
110
- expect(metadata).toMatchObject({
111
- name: 'ParentComponent',
112
- file: '/abs/path/to/ParentComponent.vue',
113
- line: 5,
114
- column: 2
115
- })
116
- })
117
-
118
- it('returns dom fallback when no instance is found', () => {
119
- const el = document.createElement('button')
120
- const metadata = extractMetadata(null, el)
121
- expect(metadata).toMatchObject({
122
- name: '<button>',
123
- file: 'unknown'
124
- })
125
- })
126
- })
@@ -1,431 +0,0 @@
1
- import { describe, it, expect, vi, afterEach } from 'vitest'
2
- import { createOverlayController } from '../overlay'
3
-
4
- describe('Overlay controller', () => {
5
- afterEach(() => {
6
- const overlay = document.querySelector('[data-vue-grab-overlay="true"]')
7
- overlay?.remove()
8
- const tooltip = document.querySelector('[data-vue-grab-tooltip="true"]')
9
- tooltip?.remove()
10
- document.body.innerHTML = ''
11
- })
12
-
13
- it('adds and removes overlay element', () => {
14
- const controller = createOverlayController(window)
15
- controller.start()
16
- expect(document.querySelector('[data-vue-grab-overlay="true"]')).toBeTruthy()
17
- controller.stop()
18
- expect(document.querySelector('[data-vue-grab-overlay="true"]')).toBeFalsy()
19
- })
20
-
21
- it('applies custom overlay styles', () => {
22
- const controller = createOverlayController(window, {
23
- overlayStyle: {
24
- border: '3px solid rgb(0, 0, 0)'
25
- }
26
- })
27
- controller.start()
28
- const overlay = document.querySelector(
29
- '[data-vue-grab-overlay="true"]'
30
- ) as HTMLDivElement
31
- expect(overlay.style.border).toBe('3px solid rgb(0, 0, 0)')
32
- controller.stop()
33
- })
34
-
35
- it('updates overlay styles at runtime', () => {
36
- const controller = createOverlayController(window)
37
- controller.start()
38
- controller.setStyle({
39
- border: '1px dotted rgb(10, 20, 30)'
40
- })
41
- const overlay = document.querySelector(
42
- '[data-vue-grab-overlay="true"]'
43
- ) as HTMLDivElement
44
- expect(overlay.style.border).toBe('1px dotted rgb(10, 20, 30)')
45
- controller.stop()
46
- })
47
-
48
- it('updates overlay position on mouse move', () => {
49
- const target = document.createElement('div')
50
- target.className = 'target'
51
- target.getBoundingClientRect = () =>
52
- ({
53
- top: 10,
54
- left: 20,
55
- width: 100,
56
- height: 50
57
- }) as DOMRect
58
- document.body.appendChild(target)
59
- Object.defineProperty(document, 'elementFromPoint', {
60
- value: vi.fn(() => target),
61
- configurable: true
62
- })
63
-
64
- const controller = createOverlayController(window)
65
- controller.start()
66
- document.dispatchEvent(new MouseEvent('mousemove', { clientX: 1, clientY: 2 }))
67
-
68
- const overlay = document.querySelector(
69
- '[data-vue-grab-overlay="true"]'
70
- ) as HTMLDivElement
71
- expect(overlay.style.top).toBe('10px')
72
- expect(overlay.style.left).toBe('20px')
73
- expect(overlay.style.width).toBe('100px')
74
- expect(overlay.style.height).toBe('50px')
75
- controller.stop()
76
- })
77
-
78
- it('shows file location tooltip on hover', () => {
79
- const target = document.createElement('div')
80
- target.className = 'target'
81
- target.getBoundingClientRect = () =>
82
- ({
83
- top: 10,
84
- left: 20,
85
- width: 100,
86
- height: 50,
87
- bottom: 60
88
- }) as DOMRect
89
- document.body.appendChild(target)
90
- ;(target as any).__vueParentComponent = {
91
- type: {
92
- name: 'TooltipComponent',
93
- __file: '/abs/path/src/components/Tooltip.vue'
94
- },
95
- vnode: {
96
- loc: {
97
- start: {
98
- line: 51,
99
- column: 3
100
- }
101
- }
102
- }
103
- }
104
- Object.defineProperty(document, 'elementFromPoint', {
105
- value: vi.fn(() => target),
106
- configurable: true
107
- })
108
-
109
- const controller = createOverlayController(window)
110
- controller.start()
111
- document.dispatchEvent(new MouseEvent('mousemove', { clientX: 1, clientY: 2 }))
112
-
113
- const tooltip = document.querySelector(
114
- '[data-vue-grab-tooltip="true"]'
115
- ) as HTMLDivElement
116
- expect(tooltip.textContent).toBe('components/Tooltip.vue:51:3')
117
- controller.stop()
118
- })
119
-
120
- it('uses rootDir for relative paths', () => {
121
- const target = document.createElement('div')
122
- target.className = 'target'
123
- target.getBoundingClientRect = () =>
124
- ({
125
- top: 10,
126
- left: 20,
127
- width: 100,
128
- height: 50,
129
- bottom: 60
130
- }) as DOMRect
131
- document.body.appendChild(target)
132
- ;(target as any).__vueParentComponent = {
133
- type: {
134
- name: 'TooltipComponent',
135
- __file: '/root/app/components/Tooltip.vue'
136
- },
137
- vnode: {
138
- loc: {
139
- start: {
140
- line: 9,
141
- column: 4
142
- }
143
- }
144
- }
145
- }
146
- Object.defineProperty(document, 'elementFromPoint', {
147
- value: vi.fn(() => target),
148
- configurable: true
149
- })
150
-
151
- const controller = createOverlayController(window, {
152
- rootDir: '/root/app'
153
- })
154
- controller.start()
155
- document.dispatchEvent(new MouseEvent('mousemove', { clientX: 1, clientY: 2 }))
156
-
157
- const tooltip = document.querySelector(
158
- '[data-vue-grab-tooltip="true"]'
159
- ) as HTMLDivElement
160
- expect(tooltip.textContent).toBe('components/Tooltip.vue:9:4')
161
- controller.stop()
162
- })
163
-
164
- it('omits line and column when missing', () => {
165
- const target = document.createElement('div')
166
- target.className = 'target'
167
- target.getBoundingClientRect = () =>
168
- ({
169
- top: 10,
170
- left: 20,
171
- width: 100,
172
- height: 50,
173
- bottom: 60
174
- }) as DOMRect
175
- document.body.appendChild(target)
176
- ;(target as any).__vueParentComponent = {
177
- type: {
178
- name: 'TooltipComponent',
179
- __file: '/abs/path/src/components/Tooltip.vue'
180
- }
181
- }
182
- Object.defineProperty(document, 'elementFromPoint', {
183
- value: vi.fn(() => target),
184
- configurable: true
185
- })
186
-
187
- const controller = createOverlayController(window)
188
- controller.start()
189
- document.dispatchEvent(new MouseEvent('mousemove', { clientX: 1, clientY: 2 }))
190
-
191
- const tooltip = document.querySelector(
192
- '[data-vue-grab-tooltip="true"]'
193
- ) as HTMLDivElement
194
- expect(tooltip.textContent).toBe('components/Tooltip.vue')
195
- controller.stop()
196
- })
197
-
198
- it('clamps tooltip position within viewport', () => {
199
- Object.defineProperty(window, 'innerWidth', { value: 120, configurable: true })
200
- Object.defineProperty(window, 'innerHeight', { value: 80, configurable: true })
201
- const target = document.createElement('div')
202
- target.className = 'target'
203
- target.getBoundingClientRect = () =>
204
- ({
205
- top: 2,
206
- left: 110,
207
- width: 50,
208
- height: 20,
209
- bottom: 22
210
- }) as DOMRect
211
- document.body.appendChild(target)
212
- ;(target as any).__vueParentComponent = {
213
- type: {
214
- name: 'TooltipComponent',
215
- __file: '/abs/path/src/components/Tooltip.vue'
216
- }
217
- }
218
- Object.defineProperty(document, 'elementFromPoint', {
219
- value: vi.fn(() => target),
220
- configurable: true
221
- })
222
-
223
- const controller = createOverlayController(window)
224
- controller.start()
225
- document.dispatchEvent(new MouseEvent('mousemove', { clientX: 1, clientY: 2 }))
226
-
227
- const tooltip = document.querySelector(
228
- '[data-vue-grab-tooltip="true"]'
229
- ) as HTMLDivElement
230
- const left = Number.parseFloat(tooltip.style.left)
231
- const top = Number.parseFloat(tooltip.style.top)
232
- expect(left).toBeGreaterThanOrEqual(0)
233
- expect(top).toBeGreaterThanOrEqual(0)
234
- controller.stop()
235
- })
236
-
237
- it('copies metadata on click', async () => {
238
- const target = document.createElement('div')
239
- target.className = 'target'
240
- document.body.appendChild(target)
241
-
242
- const writeText = vi.fn().mockResolvedValue(undefined)
243
- Object.defineProperty(navigator, 'clipboard', {
244
- value: { writeText },
245
- configurable: true
246
- })
247
-
248
- const controller = createOverlayController(window)
249
- controller.start()
250
- target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
251
-
252
- expect(writeText).toHaveBeenCalledTimes(1)
253
- const payload = writeText.mock.calls[0][0] as string
254
- expect(payload).toContain('{')
255
- controller.stop()
256
- })
257
-
258
- it('uses onCopy handler when provided', () => {
259
- const target = document.createElement('div')
260
- target.className = 'target'
261
- document.body.appendChild(target)
262
-
263
- const onCopy = vi.fn()
264
- const controller = createOverlayController(window, { onCopy })
265
- ;(target as any).__vueParentComponent = {
266
- type: {
267
- name: 'TestComponent',
268
- __file: '/abs/path/to/TestComponent.vue'
269
- }
270
- }
271
- controller.start()
272
- target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
273
-
274
- expect(onCopy).toHaveBeenCalledTimes(1)
275
- controller.stop()
276
- })
277
-
278
- it('skips copy on click when disabled', () => {
279
- const target = document.createElement('div')
280
- target.className = 'target'
281
- document.body.appendChild(target)
282
-
283
- const writeText = vi.fn().mockResolvedValue(undefined)
284
- Object.defineProperty(navigator, 'clipboard', {
285
- value: { writeText },
286
- configurable: true
287
- })
288
-
289
- const controller = createOverlayController(window, {
290
- copyOnClick: false
291
- })
292
- controller.start()
293
- target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
294
-
295
- expect(writeText).not.toHaveBeenCalled()
296
- controller.stop()
297
- })
298
-
299
- it('avoids circular metadata errors on copy', () => {
300
- const target = document.createElement('div')
301
- target.className = 'target'
302
- document.body.appendChild(target)
303
-
304
- const instance: any = {
305
- type: {
306
- name: 'CycleComponent',
307
- __file: '/abs/path/to/CycleComponent.vue'
308
- },
309
- vnode: {}
310
- }
311
- instance.vnode.component = instance
312
- ;(target as any).__vueParentComponent = instance
313
-
314
- const onCopy = vi.fn()
315
- const controller = createOverlayController(window, { onCopy })
316
- controller.start()
317
- target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
318
-
319
- expect(onCopy).toHaveBeenCalledTimes(1)
320
- controller.stop()
321
- })
322
-
323
- it('handles window references in metadata', () => {
324
- const target = document.createElement('div')
325
- target.className = 'target'
326
- document.body.appendChild(target)
327
-
328
- ;(target as any).__vueParentComponent = {
329
- type: {
330
- name: 'WindowComponent',
331
- __file: '/abs/path/to/WindowComponent.vue'
332
- },
333
- props: {
334
- win: window
335
- }
336
- }
337
-
338
- const onCopy = vi.fn()
339
- const controller = createOverlayController(window, { onCopy })
340
- controller.start()
341
- target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
342
-
343
- expect(onCopy).toHaveBeenCalledTimes(1)
344
- controller.stop()
345
- })
346
-
347
- it('limits depth and size to avoid huge payloads', () => {
348
- const target = document.createElement('div')
349
- target.className = 'target'
350
- document.body.appendChild(target)
351
-
352
- const large: any = {}
353
- let curr = large
354
- for (let i = 0; i < 20; i += 1) {
355
- curr.next = { value: i }
356
- curr = curr.next
357
- }
358
-
359
- ;(target as any).__vueParentComponent = {
360
- type: {
361
- name: 'LargeComponent',
362
- __file: '/abs/path/to/LargeComponent.vue'
363
- },
364
- props: {
365
- data: large
366
- }
367
- }
368
-
369
- const onCopy = vi.fn()
370
- const controller = createOverlayController(window, { onCopy })
371
- controller.start()
372
- target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
373
-
374
- const payload = onCopy.mock.calls[0][0] as string
375
- expect(payload).toContain('[DepthLimit]')
376
- controller.stop()
377
- })
378
-
379
- it('serializes vnode without component proxy', () => {
380
- const target = document.createElement('div')
381
- target.className = 'target'
382
- document.body.appendChild(target)
383
-
384
- const vnode = {
385
- __v_isVNode: true,
386
- type: 'div',
387
- component: { $el: target },
388
- props: { id: 'test' }
389
- }
390
-
391
- ;(target as any).__vueParentComponent = {
392
- type: {
393
- name: 'VNodeComponent',
394
- __file: '/abs/path/to/VNodeComponent.vue'
395
- },
396
- vnode
397
- }
398
-
399
- const onCopy = vi.fn()
400
- const controller = createOverlayController(window, { onCopy })
401
- controller.start()
402
- target.dispatchEvent(new MouseEvent('click', { bubbles: true }))
403
-
404
- const payload = onCopy.mock.calls[0][0] as string
405
- expect(payload).toContain('"props"')
406
- controller.stop()
407
- })
408
-
409
- it('suppresses original click event on captured click', () => {
410
- const target = document.createElement('div')
411
- target.className = 'target'
412
- document.body.appendChild(target)
413
-
414
- const originalClickHandler = vi.fn()
415
- target.addEventListener('click', originalClickHandler)
416
-
417
- const controller = createOverlayController(window)
418
- controller.start()
419
-
420
- // Simulate a click. Since we use capture, the controller should handle it first
421
- // and stop propagation, so target shouldn't receive it.
422
- target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
423
-
424
- expect(originalClickHandler).not.toHaveBeenCalled()
425
-
426
- controller.stop()
427
- // Verify normal behavior resumes
428
- target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
429
- expect(originalClickHandler).toHaveBeenCalledTimes(1)
430
- })
431
- })
@@ -1,57 +0,0 @@
1
- import { describe, it, expect, afterEach } from 'vitest'
2
- import { createToggleWidget } from '../widget'
3
-
4
- describe('Toggle widget', () => {
5
- afterEach(() => {
6
- document.querySelectorAll('[data-vue-grab-toolbar]').forEach((el) => el.remove())
7
- })
8
-
9
- it('can be dragged within the viewport', () => {
10
- Object.defineProperty(window, 'innerWidth', { value: 300, configurable: true })
11
- Object.defineProperty(window, 'innerHeight', { value: 200, configurable: true })
12
-
13
- const widget = createToggleWidget(window, {
14
- onToggle: () => {}
15
- })
16
- widget.mount()
17
-
18
- const container = document.querySelector(
19
- '[data-vue-grab-toolbar]'
20
- ) as HTMLDivElement
21
- expect(container).toBeTruthy()
22
-
23
- Object.defineProperty(container, 'offsetWidth', { value: 80, configurable: true })
24
- Object.defineProperty(container, 'offsetHeight', { value: 30, configurable: true })
25
-
26
- container.dispatchEvent(new MouseEvent('mousedown', { clientX: 290, clientY: 190 }))
27
- window.dispatchEvent(new MouseEvent('mousemove', { clientX: 250, clientY: 150 }))
28
- window.dispatchEvent(new MouseEvent('mouseup'))
29
-
30
- const left = Number.parseFloat(container.style.left)
31
- const top = Number.parseFloat(container.style.top)
32
- expect(left).toBeGreaterThanOrEqual(0)
33
- expect(top).toBeGreaterThanOrEqual(0)
34
- // Should be near the mouse position (offset preserved)
35
- // Initial: right 16, bottom 16. but logic sets left/top on drag start.
36
- })
37
-
38
- it('collapses and expands the toolbar', () => {
39
- const widget = createToggleWidget(window, {
40
- onToggle: () => {}
41
- })
42
- widget.mount()
43
-
44
- const collapse = document.querySelector(
45
- '[data-vue-grab-collapse]'
46
- ) as HTMLButtonElement
47
- const toggleWrapper = document.querySelector(
48
- '[data-vue-grab-toggle]'
49
- )?.parentElement as HTMLDivElement
50
-
51
- collapse.dispatchEvent(new MouseEvent('click', { bubbles: true }))
52
- expect(toggleWrapper.style.maxWidth).toBe('0px')
53
-
54
- collapse.dispatchEvent(new MouseEvent('click', { bubbles: true }))
55
- expect(toggleWrapper.style.maxWidth).toBe('200px')
56
- })
57
- })