@sakana-y/vue-grab 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sakana
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,12 @@
1
+ import { DeepReadonly, Ref } from 'vue';
2
+ import { GrabConfig, GrabResult } from '@sakana-y/vue-grab-shared';
3
+ export interface UseGrabReturn {
4
+ config: GrabConfig;
5
+ isActive: Readonly<Ref<boolean>>;
6
+ lastResult: DeepReadonly<Ref<GrabResult | null>>;
7
+ activate: () => void;
8
+ deactivate: () => void;
9
+ toggle: () => void;
10
+ }
11
+ export declare function useGrab(): UseGrabReturn;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/composables/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,YAAY,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAKxE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACjC,UAAU,EAAE,YAAY,CAAC,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC;IACjD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,wBAAgB,OAAO,IAAI,aAAa,CA+BvC"}
@@ -0,0 +1,27 @@
1
+ import { GrabConfig, GrabResult } from '@sakana-y/vue-grab-shared';
2
+ export declare class GrabEngine {
3
+ private config;
4
+ private overlay;
5
+ private callbacks;
6
+ private stateListeners;
7
+ private _isActive;
8
+ private prevCursor;
9
+ private handleMouseMove;
10
+ private handleClick;
11
+ private handleKeyDown;
12
+ constructor(config: GrabConfig);
13
+ get isActive(): boolean;
14
+ activate(): void;
15
+ deactivate(): void;
16
+ onGrab(cb: (result: GrabResult) => void): () => void;
17
+ onStateChange(cb: (active: boolean) => void): () => void;
18
+ toggle(): void;
19
+ destroy(): void;
20
+ updateConfig(config: Partial<GrabConfig>): void;
21
+ private shouldIgnore;
22
+ private getVueComponent;
23
+ private getComponentLabelFromInstance;
24
+ private getComponentStack;
25
+ private generateSelector;
26
+ }
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAiB,MAAM,2BAA2B,CAAC;AAevF,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,SAAS,CAAgD;IACjE,OAAO,CAAC,cAAc,CAA6C;IACnE,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAM;IAGxB,OAAO,CAAC,eAAe,CAA0C;IACjE,OAAO,CAAC,WAAW,CAA0C;IAC7D,OAAO,CAAC,aAAa,CAA6C;gBAEtD,MAAM,EAAE,UAAU;IAI9B,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,QAAQ,IAAI,IAAI;IAyDhB,UAAU,IAAI,IAAI;IAsBlB,MAAM,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI;IAKpD,aAAa,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI;IAKxD,MAAM,IAAI,IAAI;IAKd,OAAO,IAAI,IAAI;IAMf,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI;IAI/C,OAAO,CAAC,YAAY;IA8BpB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,6BAA6B;IAQrC,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,gBAAgB;CA8BzB"}
@@ -0,0 +1,58 @@
1
+ import { FloatingButtonConfig } from '@sakana-y/vue-grab-shared';
2
+ export declare const FAB_HOST_ID = "vue-grab-fab-host";
3
+ export declare class FloatingButton {
4
+ private host;
5
+ private shadowRoot;
6
+ private toolbarEl;
7
+ private btnEl;
8
+ private gearEl;
9
+ private panelEl;
10
+ private kbdEl;
11
+ private recordBtn;
12
+ private config;
13
+ private posX;
14
+ private posY;
15
+ private isDragging;
16
+ private wasDragged;
17
+ private dragPointerId;
18
+ private dragStartX;
19
+ private dragStartY;
20
+ private dragOffsetX;
21
+ private dragOffsetY;
22
+ private panelOpen;
23
+ private isRecording;
24
+ private currentHotkey;
25
+ private toggleCb;
26
+ private hotkeyChangeCb;
27
+ private boundPointerDown;
28
+ private boundPointerMove;
29
+ private boundPointerUp;
30
+ private boundDocClick;
31
+ private boundDocKeyDown;
32
+ private boundRecordKeyDown;
33
+ constructor(config: FloatingButtonConfig);
34
+ getCurrentHotkey(): string;
35
+ mount(): void;
36
+ destroy(): void;
37
+ setActive(active: boolean): void;
38
+ setHighlightColor(color: string): void;
39
+ setCurrentHotkey(combo: string): void;
40
+ onToggle(cb: () => void): void;
41
+ onHotkeyChange(cb: (combo: string) => void): void;
42
+ private onPointerDown;
43
+ private onPointerMove;
44
+ private onPointerUp;
45
+ private snapToEdge;
46
+ private applyPosition;
47
+ private getEdgeFromPosition;
48
+ private applyOrientation;
49
+ private togglePanel;
50
+ private openPanel;
51
+ private closePanel;
52
+ private positionPanel;
53
+ private toggleRecording;
54
+ private startRecording;
55
+ private stopRecording;
56
+ private updateHotkeyDisplay;
57
+ }
58
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/floating-button/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAGtE,eAAO,MAAM,WAAW,sBAAsB,CAAC;AAoO/C,qBAAa,cAAc;IACzB,OAAO,CAAC,IAAI,CAA4B;IACxC,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,MAAM,CAAuB;IAGrC,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,IAAI,CAAM;IAGlB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAM;IAC3B,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAK;IAGxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAM;IAG3B,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,cAAc,CAA0C;IAGhE,OAAO,CAAC,gBAAgB,CAA4C;IACpE,OAAO,CAAC,gBAAgB,CAA4C;IACpE,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,eAAe,CAA6C;IACpE,OAAO,CAAC,kBAAkB,CAA6C;gBAE3D,MAAM,EAAE,oBAAoB;IAiBxC,gBAAgB,IAAI,MAAM;IAI1B,KAAK,IAAI,IAAI;IAoIb,OAAO,IAAI,IAAI;IAuBf,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAShC,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAItC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKrC,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAI9B,cAAc,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAMjD,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,mBAAmB;CAI5B"}
@@ -0,0 +1,7 @@
1
+ export declare function buildCombo(e: KeyboardEvent): string;
2
+ export declare class HotkeyManager {
3
+ private cleanups;
4
+ register(combo: string, callback: () => void): void;
5
+ destroy(): void;
6
+ }
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hotkeys/index.ts"],"names":[],"mappings":"AAmBA,wBAAgB,UAAU,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CASnD;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAsB;IAEtC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAoBnD,OAAO,IAAI,IAAI;CAIhB"}
package/dist/index.cjs ADDED
@@ -0,0 +1,197 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`vue`);var t=`#4f46e5`,n=`#ffffff`,r=`Alt+Shift+G`,i={highlightColor:t,labelTextColor:n,showTagHint:!0,maxHtmlLength:1e4,filter:{ignoreSelectors:[],ignoreTags:[],skipCommonComponents:!1},floatingButton:{enabled:!1,initialPosition:`top-center`,storageKey:`vue-grab-fab-pos`,hotkeyStorageKey:`vue-grab-hotkey`}};function a(e,t){let{filter:n,floatingButton:r,...i}=t;return{...e,...i,filter:{...e.filter,...n},floatingButton:{...e.floatingButton,...r}}}var o=Symbol(`vue-grab-config`);function s(e={}){let t=a(i,e);return{install(e){e.provide(o,t)}}}var c=`vue-grab-overlay-host`,l=class{host=null;shadowRoot=null;highlightBox=null;labelEl=null;config;constructor(e){this.config=e}mount(){if(this.host)return;this.host=document.createElement(`div`),this.host.id=c,this.host.style.cssText=`position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none;`,document.body.appendChild(this.host),this.shadowRoot=this.host.attachShadow({mode:`open`}),this.host.style.setProperty(`--grab-color`,this.config.highlightColor),this.host.style.setProperty(`--grab-label-color`,this.config.labelTextColor);let e=document.createElement(`style`);e.textContent=`
2
+ .grab-highlight {
3
+ position: fixed;
4
+ pointer-events: none;
5
+ border: 2px solid var(--grab-color);
6
+ background: color-mix(in srgb, var(--grab-color) 12%, transparent);
7
+ border-radius: 2px;
8
+ transition: all 0.05s ease-out;
9
+ display: none;
10
+ box-sizing: border-box;
11
+ }
12
+ .grab-label {
13
+ position: fixed;
14
+ pointer-events: none;
15
+ background: var(--grab-color);
16
+ color: var(--grab-label-color);
17
+ font-size: 11px;
18
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
19
+ padding: 2px 6px;
20
+ border-radius: 2px;
21
+ white-space: nowrap;
22
+ display: none;
23
+ line-height: 1.4;
24
+ }
25
+ `,this.shadowRoot.appendChild(e),this.highlightBox=document.createElement(`div`),this.highlightBox.className=`grab-highlight`,this.shadowRoot.appendChild(this.highlightBox),this.labelEl=document.createElement(`div`),this.labelEl.className=`grab-label`,this.shadowRoot.appendChild(this.labelEl)}highlight(e,t){if(!this.highlightBox||!this.labelEl)return;let n=e.getBoundingClientRect();this.highlightBox.style.top=`${n.top}px`,this.highlightBox.style.left=`${n.left}px`,this.highlightBox.style.width=`${n.width}px`,this.highlightBox.style.height=`${n.height}px`,this.highlightBox.style.display=`block`,this.config.showTagHint&&t?(this.labelEl.textContent=t,n.top>24?this.labelEl.style.top=`${n.top-20-4}px`:this.labelEl.style.top=`${n.bottom+4}px`,this.labelEl.style.left=`${n.left}px`,this.labelEl.style.display=`block`):this.labelEl.style.display=`none`}clearHighlight(){this.highlightBox&&(this.highlightBox.style.display=`none`),this.labelEl&&(this.labelEl.style.display=`none`)}destroy(){this.host&&(this.host.remove(),this.host=null,this.shadowRoot=null,this.highlightBox=null,this.labelEl=null)}};function u(e){let t=e.split(`+`).map(e=>e.trim().toLowerCase());return{key:t[t.length-1],alt:t.includes(`alt`),ctrl:t.includes(`ctrl`)||t.includes(`control`),shift:t.includes(`shift`),meta:t.includes(`meta`)||t.includes(`cmd`)||t.includes(`command`)}}function d(e){let t=[];e.ctrlKey&&t.push(`Ctrl`),e.altKey&&t.push(`Alt`),e.shiftKey&&t.push(`Shift`),e.metaKey&&t.push(`Meta`);let n=e.key.length===1?e.key.toUpperCase():e.key;return t.push(n),t.join(`+`)}var f=class{cleanups=[];register(e,t){let n=u(e),r=e=>{e.key.toLowerCase()===n.key&&e.altKey===n.alt&&e.ctrlKey===n.ctrl&&e.shiftKey===n.shift&&e.metaKey===n.meta&&(e.preventDefault(),t())};document.addEventListener(`keydown`,r,{capture:!0}),this.cleanups.push(()=>document.removeEventListener(`keydown`,r,{capture:!0}))}destroy(){this.cleanups.forEach(e=>e()),this.cleanups=[]}},p=`vue-grab-fab-host`,m=3,h=`left 0.3s ease, top 0.3s ease`,g=3,_=5;function v(){return g*window.innerHeight/window.innerWidth}var y={"bottom-right":{x:97,y:85},"bottom-left":{x:3,y:85},"top-right":{x:97,y:15},"top-left":{x:3,y:15},"top-center":{x:50,y:3}};function b(e,t,n){return Math.min(Math.max(e,t),n)}function x(e,t){if(!e)return null;try{let n=localStorage.getItem(e);return n?t(n):null}catch{return null}}function S(e,t){if(e)try{localStorage.setItem(e,t)}catch{}}function C(e){return x(e,e=>{let{x:t,y:n}=JSON.parse(e);return typeof t==`number`&&typeof n==`number`?{x:t,y:n}:null})}function w(e,t,n){S(e,JSON.stringify({x:t,y:n}))}function T(e){return x(e,e=>typeof e==`string`?e:null)}function E(e,t){S(e,t)}var D=`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="22" y1="12" x2="18" y2="12"/><line x1="6" y1="12" x2="2" y2="12"/><line x1="12" y1="6" x2="12" y2="2"/><line x1="12" y1="22" x2="12" y2="18"/><line x1="12" y1="15" x2="12" y2="9"/><line x1="9" y1="12" x2="15" y2="12"/></svg>`,O=`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.32 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`,k=`
26
+ :host {
27
+ all: initial;
28
+ }
29
+ .toolbar {
30
+ display: inline-flex;
31
+ align-items: center;
32
+ height: 36px;
33
+ padding: 0 4px;
34
+ gap: 2px;
35
+ border-radius: 8px;
36
+ border: 1px solid rgba(255,255,255,0.1);
37
+ background: rgba(30,30,30,0.85);
38
+ backdrop-filter: blur(8px);
39
+ -webkit-backdrop-filter: blur(8px);
40
+ box-shadow: 0 2px 10px rgba(0,0,0,0.35);
41
+ cursor: grab;
42
+ user-select: none;
43
+ touch-action: none;
44
+ position: relative;
45
+ }
46
+ .toolbar.dragging {
47
+ cursor: grabbing;
48
+ box-shadow: 0 4px 16px rgba(0,0,0,0.5);
49
+ }
50
+ .toolbar-btn {
51
+ width: 28px;
52
+ height: 28px;
53
+ border-radius: 6px;
54
+ border: none;
55
+ background: transparent;
56
+ color: #a0a0a0;
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ cursor: pointer;
61
+ transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
62
+ }
63
+ .toolbar-btn:hover {
64
+ color: #e0e0e0;
65
+ background: rgba(255,255,255,0.08);
66
+ }
67
+ .grab-btn.active {
68
+ color: var(--grab-color, #4f46e5);
69
+ box-shadow: inset 0 0 0 1.5px var(--grab-color, #4f46e5);
70
+ background: color-mix(in srgb, var(--grab-color, #4f46e5) 12%, transparent);
71
+ }
72
+ .toolbar-divider {
73
+ width: 1px;
74
+ height: 18px;
75
+ background: rgba(255,255,255,0.12);
76
+ margin: 0 2px;
77
+ }
78
+
79
+ .panel {
80
+ position: absolute;
81
+ width: 240px;
82
+ background: rgba(25,25,25,0.94);
83
+ backdrop-filter: blur(12px);
84
+ -webkit-backdrop-filter: blur(12px);
85
+ border: 1px solid rgba(255,255,255,0.1);
86
+ border-radius: 12px;
87
+ color: #e0e0e0;
88
+ font-family: system-ui, -apple-system, sans-serif;
89
+ font-size: 13px;
90
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4);
91
+ padding: 14px;
92
+ display: none;
93
+ z-index: 1;
94
+ }
95
+ .panel.open {
96
+ display: block;
97
+ }
98
+ .panel-header {
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: space-between;
102
+ margin-bottom: 12px;
103
+ font-weight: 600;
104
+ font-size: 13px;
105
+ color: #fff;
106
+ }
107
+ .panel-close {
108
+ background: none;
109
+ border: none;
110
+ color: #888;
111
+ cursor: pointer;
112
+ font-size: 16px;
113
+ line-height: 1;
114
+ padding: 0 2px;
115
+ }
116
+ .panel-close:hover {
117
+ color: #fff;
118
+ }
119
+ .panel-label {
120
+ font-size: 11px;
121
+ color: #888;
122
+ margin-bottom: 6px;
123
+ text-transform: uppercase;
124
+ letter-spacing: 0.5px;
125
+ }
126
+ .hotkey-row {
127
+ display: flex;
128
+ align-items: center;
129
+ gap: 8px;
130
+ }
131
+ kbd {
132
+ display: inline-flex;
133
+ align-items: center;
134
+ background: rgba(255,255,255,0.08);
135
+ border: 1px solid rgba(255,255,255,0.15);
136
+ border-radius: 6px;
137
+ padding: 4px 10px;
138
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
139
+ font-size: 12px;
140
+ color: #ddd;
141
+ min-width: 80px;
142
+ justify-content: center;
143
+ flex: 1;
144
+ }
145
+ kbd.recording {
146
+ border-color: var(--grab-color, #4f46e5);
147
+ box-shadow: 0 0 0 1px var(--grab-color, #4f46e5);
148
+ animation: pulse 1.5s ease-in-out infinite;
149
+ }
150
+ @keyframes pulse {
151
+ 0%, 100% { opacity: 1; }
152
+ 50% { opacity: 0.6; }
153
+ }
154
+ .record-btn {
155
+ background: rgba(255,255,255,0.08);
156
+ border: 1px solid rgba(255,255,255,0.15);
157
+ border-radius: 6px;
158
+ color: #ccc;
159
+ padding: 4px 10px;
160
+ font-size: 12px;
161
+ cursor: pointer;
162
+ white-space: nowrap;
163
+ font-family: inherit;
164
+ }
165
+ .record-btn:hover {
166
+ background: rgba(255,255,255,0.14);
167
+ color: #fff;
168
+ }
169
+ .panel-hint {
170
+ margin-top: 12px;
171
+ font-size: 11px;
172
+ color: #555;
173
+ text-align: center;
174
+ }
175
+ .toolbar.vertical {
176
+ flex-direction: column;
177
+ height: auto;
178
+ width: 36px;
179
+ padding: 4px 0;
180
+ }
181
+ .toolbar.vertical .toolbar-divider {
182
+ width: 18px;
183
+ height: 1px;
184
+ margin: 2px 0;
185
+ }
186
+ `,A=class{host=null;shadowRoot=null;toolbarEl=null;btnEl=null;gearEl=null;panelEl=null;kbdEl=null;recordBtn=null;config;posX=97;posY=85;isDragging=!1;wasDragged=!1;dragPointerId=-1;dragStartX=0;dragStartY=0;dragOffsetX=0;dragOffsetY=0;panelOpen=!1;isRecording=!1;currentHotkey=``;toggleCb=null;hotkeyChangeCb=null;boundPointerDown=null;boundPointerMove=null;boundPointerUp=null;boundDocClick=null;boundDocKeyDown=null;boundRecordKeyDown=null;constructor(e){this.config=e;let t=y[e.initialPosition]??y[`bottom-right`],n=C(e.storageKey);n?(this.posX=n.x,this.posY=n.y):(this.posX=t.x,this.posY=t.y,this.posX<=_?this.posX=v():this.posX>=100-_&&(this.posX=100-v())),this.currentHotkey=T(e.hotkeyStorageKey)??``}getCurrentHotkey(){return this.currentHotkey}mount(){if(this.host)return;this.host=document.createElement(`div`),this.host.id=p,this.host.style.cssText=`position:fixed;z-index:2147483646;pointer-events:auto;transform:translate(-50%,-50%);transition:${h};`,this.applyPosition(),document.body.appendChild(this.host),this.shadowRoot=this.host.attachShadow({mode:`open`});let e=document.createElement(`style`);e.textContent=k,this.shadowRoot.appendChild(e),this.toolbarEl=document.createElement(`div`),this.toolbarEl.className=`toolbar`,this.btnEl=document.createElement(`div`),this.btnEl.className=`toolbar-btn grab-btn`,this.btnEl.innerHTML=D,this.toolbarEl.appendChild(this.btnEl);let t=document.createElement(`div`);t.className=`toolbar-divider`,this.toolbarEl.appendChild(t),this.gearEl=document.createElement(`div`),this.gearEl.className=`toolbar-btn gear-btn`,this.gearEl.innerHTML=O,this.toolbarEl.appendChild(this.gearEl),this.panelEl=document.createElement(`div`),this.panelEl.className=`panel`,this.panelEl.innerHTML=`
187
+ <div class="panel-header">
188
+ <span>Settings</span>
189
+ <button class="panel-close" data-action="close">&times;</button>
190
+ </div>
191
+ <div class="panel-label">Hotkey</div>
192
+ <div class="hotkey-row">
193
+ <kbd></kbd>
194
+ <button class="record-btn">Record</button>
195
+ </div>
196
+ <div class="panel-hint">Drag toolbar to reposition</div>
197
+ `,this.toolbarEl.appendChild(this.panelEl),this.shadowRoot.appendChild(this.toolbarEl),this.kbdEl=this.panelEl.querySelector(`kbd`),this.recordBtn=this.panelEl.querySelector(`.record-btn`),this.updateHotkeyDisplay(),this.applyOrientation(this.getEdgeFromPosition()),this.boundPointerDown=this.onPointerDown.bind(this),this.boundPointerMove=this.onPointerMove.bind(this),this.boundPointerUp=this.onPointerUp.bind(this),this.toolbarEl.addEventListener(`pointerdown`,this.boundPointerDown),this.toolbarEl.addEventListener(`pointermove`,this.boundPointerMove),this.toolbarEl.addEventListener(`pointerup`,this.boundPointerUp),this.btnEl.addEventListener(`click`,e=>{if(e.stopPropagation(),!this.wasDragged){if(this.panelOpen){this.closePanel();return}this.toggleCb?.()}}),this.gearEl.addEventListener(`click`,e=>{e.stopPropagation(),!this.wasDragged&&this.togglePanel()}),this.panelEl.querySelector(`[data-action=close]`).addEventListener(`click`,e=>{e.stopPropagation(),this.closePanel()}),this.recordBtn.addEventListener(`click`,e=>{e.stopPropagation(),this.toggleRecording()}),this.boundDocClick=e=>{this.panelOpen&&(e.composedPath().includes(this.host)||this.closePanel())},document.addEventListener(`click`,this.boundDocClick,{capture:!0}),this.boundDocKeyDown=e=>{e.key===`Escape`&&(this.isRecording?(this.stopRecording(),e.preventDefault(),e.stopPropagation()):this.panelOpen&&(this.closePanel(),e.preventDefault(),e.stopPropagation()))},document.addEventListener(`keydown`,this.boundDocKeyDown,{capture:!0})}destroy(){this.boundDocClick&&document.removeEventListener(`click`,this.boundDocClick,{capture:!0}),this.boundDocKeyDown&&document.removeEventListener(`keydown`,this.boundDocKeyDown,{capture:!0}),this.boundRecordKeyDown&&document.removeEventListener(`keydown`,this.boundRecordKeyDown,{capture:!0}),this.host&&(this.host.remove(),this.host=null,this.shadowRoot=null,this.toolbarEl=null,this.btnEl=null,this.gearEl=null,this.panelEl=null,this.kbdEl=null,this.recordBtn=null)}setActive(e){this.btnEl&&(e?this.btnEl.classList.add(`active`):this.btnEl.classList.remove(`active`))}setHighlightColor(e){this.host?.style.setProperty(`--grab-color`,e)}setCurrentHotkey(e){this.currentHotkey=e,this.updateHotkeyDisplay()}onToggle(e){this.toggleCb=e}onHotkeyChange(e){this.hotkeyChangeCb=e}onPointerDown(e){if(e.button!==0)return;this.isDragging=!0,this.wasDragged=!1,this.dragPointerId=e.pointerId,this.dragStartX=e.clientX,this.dragStartY=e.clientY;let t=this.toolbarEl.getBoundingClientRect();this.dragOffsetX=e.clientX-t.left-t.width/2,this.dragOffsetY=e.clientY-t.top-t.height/2,this.host.style.transition=`none`}onPointerMove(e){if(!this.isDragging)return;let t=e.clientX-this.dragStartX,n=e.clientY-this.dragStartY;!this.wasDragged&&Math.abs(t)<m&&Math.abs(n)<m||(this.wasDragged||(this.wasDragged=!0,this.toolbarEl.setPointerCapture(this.dragPointerId),this.toolbarEl.classList.add(`dragging`)),this.posX=b((e.clientX-this.dragOffsetX)/window.innerWidth*100,2,98),this.posY=b((e.clientY-this.dragOffsetY)/window.innerHeight*100,2,98),this.applyPosition())}onPointerUp(e){this.isDragging&&(this.isDragging=!1,this.toolbarEl.releasePointerCapture(e.pointerId),this.toolbarEl.classList.remove(`dragging`),this.wasDragged&&(this.host.style.transition=h,this.snapToEdge(),w(this.config.storageKey,this.posX,this.posY)))}snapToEdge(){let e=this.getEdgeFromPosition(),t=v();e===`right`?this.posX=100-t:e===`bottom`?this.posY=100-g:e===`top`?this.posY=g:this.posX=t,this.applyPosition(),this.applyOrientation(e),this.panelOpen&&this.positionPanel()}applyPosition(){this.host&&(this.host.style.left=`${this.posX}%`,this.host.style.top=`${this.posY}%`)}getEdgeFromPosition(){let e=Math.atan2(this.posY-50,this.posX-50)*180/Math.PI;return e>=-45&&e<45?`right`:e>=45&&e<135?`bottom`:e>=-135&&e<-45?`top`:`left`}applyOrientation(e){if(!this.toolbarEl)return;let t=e===`left`||e===`right`;this.toolbarEl.classList.toggle(`vertical`,t)}togglePanel(){this.panelOpen?this.closePanel():this.openPanel()}openPanel(){this.panelEl&&(this.panelOpen=!0,this.positionPanel(),this.panelEl.classList.add(`open`))}closePanel(){this.panelEl&&(this.panelOpen=!1,this.panelEl.classList.remove(`open`),this.isRecording&&this.stopRecording())}positionPanel(){this.panelEl&&(this.posX>50?(this.panelEl.style.right=`calc(100% + 8px)`,this.panelEl.style.left=`auto`):(this.panelEl.style.left=`calc(100% + 8px)`,this.panelEl.style.right=`auto`),this.posY>70?(this.panelEl.style.bottom=`-4px`,this.panelEl.style.top=`auto`):(this.panelEl.style.top=`-4px`,this.panelEl.style.bottom=`auto`))}toggleRecording(){this.isRecording?this.stopRecording():this.startRecording()}startRecording(){this.isRecording=!0,this.kbdEl.textContent=`Press keys…`,this.kbdEl.classList.add(`recording`),this.recordBtn.textContent=`Cancel`,this.boundRecordKeyDown=e=>{if(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),[`Alt`,`Control`,`Shift`,`Meta`].includes(e.key))return;let t=d(e);this.currentHotkey=t,this.stopRecording(),E(this.config.hotkeyStorageKey,t),this.hotkeyChangeCb?.(t)},document.addEventListener(`keydown`,this.boundRecordKeyDown,{capture:!0})}stopRecording(){this.isRecording=!1,this.boundRecordKeyDown&&=(document.removeEventListener(`keydown`,this.boundRecordKeyDown,{capture:!0}),null),this.updateHotkeyDisplay(),this.recordBtn&&(this.recordBtn.textContent=`Record`),this.kbdEl&&this.kbdEl.classList.remove(`recording`)}updateHotkeyDisplay(){this.kbdEl&&(this.kbdEl.textContent=this.currentHotkey||`None`)}},j=new Set([`header`,`nav`,`footer`,`aside`,`main`,`layout`,`sidebar`]),M=class{config;overlay=null;callbacks=new Set;stateListeners=new Set;_isActive=!1;prevCursor=``;handleMouseMove=null;handleClick=null;handleKeyDown=null;constructor(e){this.config=e}get isActive(){return this._isActive}activate(){this._isActive||(this._isActive=!0,this.stateListeners.forEach(e=>e(!0)),this.overlay=new l(this.config),this.overlay.mount(),this.prevCursor=document.body.style.cursor,document.body.style.cursor=`crosshair`,this.handleMouseMove=e=>{let t=document.elementFromPoint(e.clientX,e.clientY);if(!t||this.shouldIgnore(t)){this.overlay?.clearHighlight();return}let n=this.getVueComponent(t);this.overlay?.highlight(t,this.getComponentLabelFromInstance(t,n))},this.handleClick=e=>{e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation();let t=document.elementFromPoint(e.clientX,e.clientY);if(!t||this.shouldIgnore(t))return;let n=t.outerHTML;this.config.maxHtmlLength>0&&n.length>this.config.maxHtmlLength&&(n=n.slice(0,this.config.maxHtmlLength)+`<!-- truncated -->`);let r={element:t,html:n,componentStack:this.getComponentStack(t),selector:this.generateSelector(t)};this.callbacks.forEach(e=>e(r)),this.deactivate()},this.handleKeyDown=e=>{e.key===`Escape`&&(e.preventDefault(),this.deactivate())},document.addEventListener(`mousemove`,this.handleMouseMove,{capture:!0}),document.addEventListener(`click`,this.handleClick,{capture:!0}),document.addEventListener(`keydown`,this.handleKeyDown,{capture:!0}))}deactivate(){this._isActive&&(this._isActive=!1,this.stateListeners.forEach(e=>e(!1)),this.handleMouseMove&&document.removeEventListener(`mousemove`,this.handleMouseMove,{capture:!0}),this.handleClick&&document.removeEventListener(`click`,this.handleClick,{capture:!0}),this.handleKeyDown&&document.removeEventListener(`keydown`,this.handleKeyDown,{capture:!0}),this.handleMouseMove=null,this.handleClick=null,this.handleKeyDown=null,this.overlay?.destroy(),this.overlay=null,document.body.style.cursor=this.prevCursor)}onGrab(e){return this.callbacks.add(e),()=>this.callbacks.delete(e)}onStateChange(e){return this.stateListeners.add(e),()=>this.stateListeners.delete(e)}toggle(){this._isActive?this.deactivate():this.activate()}destroy(){this.deactivate(),this.callbacks.clear(),this.stateListeners.clear()}updateConfig(e){this.config=a(this.config,e)}shouldIgnore(e){if(e.closest(`#vue-grab-overlay-host, #vue-grab-fab-host`))return!0;let t=e.tagName.toLowerCase();if(this.config.filter.ignoreTags.includes(t))return!0;for(let t of this.config.filter.ignoreSelectors)try{if(e.matches(t))return!0}catch{}if(this.config.filter.skipCommonComponents){let t=this.getVueComponent(e);if(t){let e=t.type.name||t.type.__name||``;if(j.has(e.toLowerCase()))return!0}}return!1}getVueComponent(e){let t=e;for(;t;){let e=t.__vueParentComponent||t.__vue_app__?._instance;if(e)return e;t=t.parentElement}return null}getComponentLabelFromInstance(e,t){if(t){let e=t.type?.name||t.type?.__name;if(e)return`<${e}>`}return`<${e.tagName.toLowerCase()}>`}getComponentStack(e){let t=[],n=e;for(;n;){let e=n.__vueParentComponent||n.__vue_app__?._instance;if(e){let n=e.type?.name||e.type?.__name||`Anonymous`,r=e.type?.__file,i={name:n};r&&(i.filePath=r),t.push(i)}n=n.parentElement}return t}generateSelector(e){if(e.id)return`#${CSS.escape(e.id)}`;let t=[],n=e;for(;n&&n!==document.body&&n!==document.documentElement;){let e=n.tagName.toLowerCase();if(n.id){t.unshift(`#${CSS.escape(n.id)}`);break}if(n.className&&typeof n.className==`string`){let t=n.className.trim().split(/\s+/).filter(Boolean);t.length>0&&(e+=`.${t.slice(0,2).map(e=>CSS.escape(e)).join(`.`)}`)}t.unshift(e),n=n.parentElement}return t.join(` > `)}};function N(e){let t=new M(e),n=new f,i=null;if(e.floatingButton.enabled){let r=new A(e.floatingButton);i=r;let a=r.getCurrentHotkey()||`Alt+Shift+G`;n.register(a,()=>t.toggle()),r.setHighlightColor(e.highlightColor),r.setCurrentHotkey(a),r.onToggle(()=>t.toggle()),r.onHotkeyChange(e=>{n.destroy(),n.register(e,()=>t.toggle()),r.setCurrentHotkey(e)}),t.onStateChange(e=>r.setActive(e)),r.mount()}else n.register(r,()=>t.toggle());return{engine:t,hotkeys:n,fab:i,destroy(){t.destroy(),n.destroy(),i?.destroy()}}}function P(){let t=(0,e.inject)(o,{...i}),n=(0,e.ref)(!1),r=(0,e.ref)(null),a=N(t),{engine:s}=a,c=s.onGrab(e=>{r.value=e}),l=s.onStateChange(e=>{n.value=e});return(0,e.onUnmounted)(()=>{c(),l(),a.destroy()}),{config:t,isActive:(0,e.readonly)(n),lastResult:(0,e.readonly)(r),activate:()=>s.activate(),deactivate:()=>s.deactivate(),toggle:()=>s.toggle()}}function F(e={}){let t=N(a(i,e));return{activate:()=>t.engine.activate(),deactivate:()=>t.engine.deactivate(),onGrab:e=>t.engine.onGrab(e),destroy:()=>t.destroy()}}exports.FAB_HOST_ID=p,exports.FloatingButton=A,exports.VUE_GRAB_CONFIG_KEY=o,exports.createVueGrab=s,exports.init=F,exports.useGrab=P;
@@ -0,0 +1,6 @@
1
+ export { createVueGrab, VUE_GRAB_CONFIG_KEY } from './plugin';
2
+ export { useGrab } from './composables';
3
+ export { init } from './init';
4
+ export { FloatingButton, FAB_HOST_ID } from './floating-button';
5
+ export type { GrabConfig, GrabResult, ComponentInfo, GrabFilterConfig, FloatingButtonConfig, } from '@sakana-y/vue-grab-shared';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,2BAA2B,CAAC"}
package/dist/index.mjs ADDED
@@ -0,0 +1,493 @@
1
+ import { inject as e, onUnmounted as t, readonly as n, ref as r } from "vue";
2
+ //#region ../shared/dist/index.mjs
3
+ var i = "#4f46e5", a = "#ffffff", o = "Alt+Shift+G", s = {
4
+ highlightColor: i,
5
+ labelTextColor: a,
6
+ showTagHint: !0,
7
+ maxHtmlLength: 1e4,
8
+ filter: {
9
+ ignoreSelectors: [],
10
+ ignoreTags: [],
11
+ skipCommonComponents: !1
12
+ },
13
+ floatingButton: {
14
+ enabled: !1,
15
+ initialPosition: "top-center",
16
+ storageKey: "vue-grab-fab-pos",
17
+ hotkeyStorageKey: "vue-grab-hotkey"
18
+ }
19
+ };
20
+ function c(e, t) {
21
+ let { filter: n, floatingButton: r, ...i } = t;
22
+ return {
23
+ ...e,
24
+ ...i,
25
+ filter: {
26
+ ...e.filter,
27
+ ...n
28
+ },
29
+ floatingButton: {
30
+ ...e.floatingButton,
31
+ ...r
32
+ }
33
+ };
34
+ }
35
+ //#endregion
36
+ //#region src/plugin.ts
37
+ var l = Symbol("vue-grab-config");
38
+ function u(e = {}) {
39
+ let t = c(s, e);
40
+ return { install(e) {
41
+ e.provide(l, t);
42
+ } };
43
+ }
44
+ //#endregion
45
+ //#region src/overlay/index.ts
46
+ var d = "vue-grab-overlay-host", f = class {
47
+ host = null;
48
+ shadowRoot = null;
49
+ highlightBox = null;
50
+ labelEl = null;
51
+ config;
52
+ constructor(e) {
53
+ this.config = e;
54
+ }
55
+ mount() {
56
+ if (this.host) return;
57
+ this.host = document.createElement("div"), this.host.id = d, this.host.style.cssText = "position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none;", document.body.appendChild(this.host), this.shadowRoot = this.host.attachShadow({ mode: "open" }), this.host.style.setProperty("--grab-color", this.config.highlightColor), this.host.style.setProperty("--grab-label-color", this.config.labelTextColor);
58
+ let e = document.createElement("style");
59
+ e.textContent = "\n .grab-highlight {\n position: fixed;\n pointer-events: none;\n border: 2px solid var(--grab-color);\n background: color-mix(in srgb, var(--grab-color) 12%, transparent);\n border-radius: 2px;\n transition: all 0.05s ease-out;\n display: none;\n box-sizing: border-box;\n }\n .grab-label {\n position: fixed;\n pointer-events: none;\n background: var(--grab-color);\n color: var(--grab-label-color);\n font-size: 11px;\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n padding: 2px 6px;\n border-radius: 2px;\n white-space: nowrap;\n display: none;\n line-height: 1.4;\n }\n ", this.shadowRoot.appendChild(e), this.highlightBox = document.createElement("div"), this.highlightBox.className = "grab-highlight", this.shadowRoot.appendChild(this.highlightBox), this.labelEl = document.createElement("div"), this.labelEl.className = "grab-label", this.shadowRoot.appendChild(this.labelEl);
60
+ }
61
+ highlight(e, t) {
62
+ if (!this.highlightBox || !this.labelEl) return;
63
+ let n = e.getBoundingClientRect();
64
+ this.highlightBox.style.top = `${n.top}px`, this.highlightBox.style.left = `${n.left}px`, this.highlightBox.style.width = `${n.width}px`, this.highlightBox.style.height = `${n.height}px`, this.highlightBox.style.display = "block", this.config.showTagHint && t ? (this.labelEl.textContent = t, n.top > 24 ? this.labelEl.style.top = `${n.top - 20 - 4}px` : this.labelEl.style.top = `${n.bottom + 4}px`, this.labelEl.style.left = `${n.left}px`, this.labelEl.style.display = "block") : this.labelEl.style.display = "none";
65
+ }
66
+ clearHighlight() {
67
+ this.highlightBox && (this.highlightBox.style.display = "none"), this.labelEl && (this.labelEl.style.display = "none");
68
+ }
69
+ destroy() {
70
+ this.host && (this.host.remove(), this.host = null, this.shadowRoot = null, this.highlightBox = null, this.labelEl = null);
71
+ }
72
+ };
73
+ //#endregion
74
+ //#region src/hotkeys/index.ts
75
+ function p(e) {
76
+ let t = e.split("+").map((e) => e.trim().toLowerCase());
77
+ return {
78
+ key: t[t.length - 1],
79
+ alt: t.includes("alt"),
80
+ ctrl: t.includes("ctrl") || t.includes("control"),
81
+ shift: t.includes("shift"),
82
+ meta: t.includes("meta") || t.includes("cmd") || t.includes("command")
83
+ };
84
+ }
85
+ function m(e) {
86
+ let t = [];
87
+ e.ctrlKey && t.push("Ctrl"), e.altKey && t.push("Alt"), e.shiftKey && t.push("Shift"), e.metaKey && t.push("Meta");
88
+ let n = e.key.length === 1 ? e.key.toUpperCase() : e.key;
89
+ return t.push(n), t.join("+");
90
+ }
91
+ var h = class {
92
+ cleanups = [];
93
+ register(e, t) {
94
+ let n = p(e), r = (e) => {
95
+ e.key.toLowerCase() === n.key && e.altKey === n.alt && e.ctrlKey === n.ctrl && e.shiftKey === n.shift && e.metaKey === n.meta && (e.preventDefault(), t());
96
+ };
97
+ document.addEventListener("keydown", r, { capture: !0 }), this.cleanups.push(() => document.removeEventListener("keydown", r, { capture: !0 }));
98
+ }
99
+ destroy() {
100
+ this.cleanups.forEach((e) => e()), this.cleanups = [];
101
+ }
102
+ }, g = "vue-grab-fab-host", _ = 3, v = "left 0.3s ease, top 0.3s ease", y = 3, b = 5;
103
+ function x() {
104
+ return y * window.innerHeight / window.innerWidth;
105
+ }
106
+ var S = {
107
+ "bottom-right": {
108
+ x: 97,
109
+ y: 85
110
+ },
111
+ "bottom-left": {
112
+ x: 3,
113
+ y: 85
114
+ },
115
+ "top-right": {
116
+ x: 97,
117
+ y: 15
118
+ },
119
+ "top-left": {
120
+ x: 3,
121
+ y: 15
122
+ },
123
+ "top-center": {
124
+ x: 50,
125
+ y: 3
126
+ }
127
+ };
128
+ function C(e, t, n) {
129
+ return Math.min(Math.max(e, t), n);
130
+ }
131
+ function w(e, t) {
132
+ if (!e) return null;
133
+ try {
134
+ let n = localStorage.getItem(e);
135
+ return n ? t(n) : null;
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+ function T(e, t) {
141
+ if (e) try {
142
+ localStorage.setItem(e, t);
143
+ } catch {}
144
+ }
145
+ function E(e) {
146
+ return w(e, (e) => {
147
+ let { x: t, y: n } = JSON.parse(e);
148
+ return typeof t == "number" && typeof n == "number" ? {
149
+ x: t,
150
+ y: n
151
+ } : null;
152
+ });
153
+ }
154
+ function D(e, t, n) {
155
+ T(e, JSON.stringify({
156
+ x: t,
157
+ y: n
158
+ }));
159
+ }
160
+ function O(e) {
161
+ return w(e, (e) => typeof e == "string" ? e : null);
162
+ }
163
+ function k(e, t) {
164
+ T(e, t);
165
+ }
166
+ var A = "<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"22\" y1=\"12\" x2=\"18\" y2=\"12\"/><line x1=\"6\" y1=\"12\" x2=\"2\" y2=\"12\"/><line x1=\"12\" y1=\"6\" x2=\"12\" y2=\"2\"/><line x1=\"12\" y1=\"22\" x2=\"12\" y2=\"18\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"9\"/><line x1=\"9\" y1=\"12\" x2=\"15\" y2=\"12\"/></svg>", j = "<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.32 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>", M = "\n :host {\n all: initial;\n }\n .toolbar {\n display: inline-flex;\n align-items: center;\n height: 36px;\n padding: 0 4px;\n gap: 2px;\n border-radius: 8px;\n border: 1px solid rgba(255,255,255,0.1);\n background: rgba(30,30,30,0.85);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n box-shadow: 0 2px 10px rgba(0,0,0,0.35);\n cursor: grab;\n user-select: none;\n touch-action: none;\n position: relative;\n }\n .toolbar.dragging {\n cursor: grabbing;\n box-shadow: 0 4px 16px rgba(0,0,0,0.5);\n }\n .toolbar-btn {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n border: none;\n background: transparent;\n color: #a0a0a0;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;\n }\n .toolbar-btn:hover {\n color: #e0e0e0;\n background: rgba(255,255,255,0.08);\n }\n .grab-btn.active {\n color: var(--grab-color, #4f46e5);\n box-shadow: inset 0 0 0 1.5px var(--grab-color, #4f46e5);\n background: color-mix(in srgb, var(--grab-color, #4f46e5) 12%, transparent);\n }\n .toolbar-divider {\n width: 1px;\n height: 18px;\n background: rgba(255,255,255,0.12);\n margin: 0 2px;\n }\n\n .panel {\n position: absolute;\n width: 240px;\n background: rgba(25,25,25,0.94);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid rgba(255,255,255,0.1);\n border-radius: 12px;\n color: #e0e0e0;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 13px;\n box-shadow: 0 8px 32px rgba(0,0,0,0.4);\n padding: 14px;\n display: none;\n z-index: 1;\n }\n .panel.open {\n display: block;\n }\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 12px;\n font-weight: 600;\n font-size: 13px;\n color: #fff;\n }\n .panel-close {\n background: none;\n border: none;\n color: #888;\n cursor: pointer;\n font-size: 16px;\n line-height: 1;\n padding: 0 2px;\n }\n .panel-close:hover {\n color: #fff;\n }\n .panel-label {\n font-size: 11px;\n color: #888;\n margin-bottom: 6px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n .hotkey-row {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n kbd {\n display: inline-flex;\n align-items: center;\n background: rgba(255,255,255,0.08);\n border: 1px solid rgba(255,255,255,0.15);\n border-radius: 6px;\n padding: 4px 10px;\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 12px;\n color: #ddd;\n min-width: 80px;\n justify-content: center;\n flex: 1;\n }\n kbd.recording {\n border-color: var(--grab-color, #4f46e5);\n box-shadow: 0 0 0 1px var(--grab-color, #4f46e5);\n animation: pulse 1.5s ease-in-out infinite;\n }\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.6; }\n }\n .record-btn {\n background: rgba(255,255,255,0.08);\n border: 1px solid rgba(255,255,255,0.15);\n border-radius: 6px;\n color: #ccc;\n padding: 4px 10px;\n font-size: 12px;\n cursor: pointer;\n white-space: nowrap;\n font-family: inherit;\n }\n .record-btn:hover {\n background: rgba(255,255,255,0.14);\n color: #fff;\n }\n .panel-hint {\n margin-top: 12px;\n font-size: 11px;\n color: #555;\n text-align: center;\n }\n .toolbar.vertical {\n flex-direction: column;\n height: auto;\n width: 36px;\n padding: 4px 0;\n }\n .toolbar.vertical .toolbar-divider {\n width: 18px;\n height: 1px;\n margin: 2px 0;\n }\n", N = class {
167
+ host = null;
168
+ shadowRoot = null;
169
+ toolbarEl = null;
170
+ btnEl = null;
171
+ gearEl = null;
172
+ panelEl = null;
173
+ kbdEl = null;
174
+ recordBtn = null;
175
+ config;
176
+ posX = 97;
177
+ posY = 85;
178
+ isDragging = !1;
179
+ wasDragged = !1;
180
+ dragPointerId = -1;
181
+ dragStartX = 0;
182
+ dragStartY = 0;
183
+ dragOffsetX = 0;
184
+ dragOffsetY = 0;
185
+ panelOpen = !1;
186
+ isRecording = !1;
187
+ currentHotkey = "";
188
+ toggleCb = null;
189
+ hotkeyChangeCb = null;
190
+ boundPointerDown = null;
191
+ boundPointerMove = null;
192
+ boundPointerUp = null;
193
+ boundDocClick = null;
194
+ boundDocKeyDown = null;
195
+ boundRecordKeyDown = null;
196
+ constructor(e) {
197
+ this.config = e;
198
+ let t = S[e.initialPosition] ?? S["bottom-right"], n = E(e.storageKey);
199
+ n ? (this.posX = n.x, this.posY = n.y) : (this.posX = t.x, this.posY = t.y, this.posX <= b ? this.posX = x() : this.posX >= 100 - b && (this.posX = 100 - x())), this.currentHotkey = O(e.hotkeyStorageKey) ?? "";
200
+ }
201
+ getCurrentHotkey() {
202
+ return this.currentHotkey;
203
+ }
204
+ mount() {
205
+ if (this.host) return;
206
+ this.host = document.createElement("div"), this.host.id = g, this.host.style.cssText = `position:fixed;z-index:2147483646;pointer-events:auto;transform:translate(-50%,-50%);transition:${v};`, this.applyPosition(), document.body.appendChild(this.host), this.shadowRoot = this.host.attachShadow({ mode: "open" });
207
+ let e = document.createElement("style");
208
+ e.textContent = M, this.shadowRoot.appendChild(e), this.toolbarEl = document.createElement("div"), this.toolbarEl.className = "toolbar", this.btnEl = document.createElement("div"), this.btnEl.className = "toolbar-btn grab-btn", this.btnEl.innerHTML = A, this.toolbarEl.appendChild(this.btnEl);
209
+ let t = document.createElement("div");
210
+ t.className = "toolbar-divider", this.toolbarEl.appendChild(t), this.gearEl = document.createElement("div"), this.gearEl.className = "toolbar-btn gear-btn", this.gearEl.innerHTML = j, this.toolbarEl.appendChild(this.gearEl), this.panelEl = document.createElement("div"), this.panelEl.className = "panel", this.panelEl.innerHTML = "\n <div class=\"panel-header\">\n <span>Settings</span>\n <button class=\"panel-close\" data-action=\"close\">&times;</button>\n </div>\n <div class=\"panel-label\">Hotkey</div>\n <div class=\"hotkey-row\">\n <kbd></kbd>\n <button class=\"record-btn\">Record</button>\n </div>\n <div class=\"panel-hint\">Drag toolbar to reposition</div>\n ", this.toolbarEl.appendChild(this.panelEl), this.shadowRoot.appendChild(this.toolbarEl), this.kbdEl = this.panelEl.querySelector("kbd"), this.recordBtn = this.panelEl.querySelector(".record-btn"), this.updateHotkeyDisplay(), this.applyOrientation(this.getEdgeFromPosition()), this.boundPointerDown = this.onPointerDown.bind(this), this.boundPointerMove = this.onPointerMove.bind(this), this.boundPointerUp = this.onPointerUp.bind(this), this.toolbarEl.addEventListener("pointerdown", this.boundPointerDown), this.toolbarEl.addEventListener("pointermove", this.boundPointerMove), this.toolbarEl.addEventListener("pointerup", this.boundPointerUp), this.btnEl.addEventListener("click", (e) => {
211
+ if (e.stopPropagation(), !this.wasDragged) {
212
+ if (this.panelOpen) {
213
+ this.closePanel();
214
+ return;
215
+ }
216
+ this.toggleCb?.();
217
+ }
218
+ }), this.gearEl.addEventListener("click", (e) => {
219
+ e.stopPropagation(), !this.wasDragged && this.togglePanel();
220
+ }), this.panelEl.querySelector("[data-action=close]").addEventListener("click", (e) => {
221
+ e.stopPropagation(), this.closePanel();
222
+ }), this.recordBtn.addEventListener("click", (e) => {
223
+ e.stopPropagation(), this.toggleRecording();
224
+ }), this.boundDocClick = (e) => {
225
+ this.panelOpen && (e.composedPath().includes(this.host) || this.closePanel());
226
+ }, document.addEventListener("click", this.boundDocClick, { capture: !0 }), this.boundDocKeyDown = (e) => {
227
+ e.key === "Escape" && (this.isRecording ? (this.stopRecording(), e.preventDefault(), e.stopPropagation()) : this.panelOpen && (this.closePanel(), e.preventDefault(), e.stopPropagation()));
228
+ }, document.addEventListener("keydown", this.boundDocKeyDown, { capture: !0 });
229
+ }
230
+ destroy() {
231
+ this.boundDocClick && document.removeEventListener("click", this.boundDocClick, { capture: !0 }), this.boundDocKeyDown && document.removeEventListener("keydown", this.boundDocKeyDown, { capture: !0 }), this.boundRecordKeyDown && document.removeEventListener("keydown", this.boundRecordKeyDown, { capture: !0 }), this.host && (this.host.remove(), this.host = null, this.shadowRoot = null, this.toolbarEl = null, this.btnEl = null, this.gearEl = null, this.panelEl = null, this.kbdEl = null, this.recordBtn = null);
232
+ }
233
+ setActive(e) {
234
+ this.btnEl && (e ? this.btnEl.classList.add("active") : this.btnEl.classList.remove("active"));
235
+ }
236
+ setHighlightColor(e) {
237
+ this.host?.style.setProperty("--grab-color", e);
238
+ }
239
+ setCurrentHotkey(e) {
240
+ this.currentHotkey = e, this.updateHotkeyDisplay();
241
+ }
242
+ onToggle(e) {
243
+ this.toggleCb = e;
244
+ }
245
+ onHotkeyChange(e) {
246
+ this.hotkeyChangeCb = e;
247
+ }
248
+ onPointerDown(e) {
249
+ if (e.button !== 0) return;
250
+ this.isDragging = !0, this.wasDragged = !1, this.dragPointerId = e.pointerId, this.dragStartX = e.clientX, this.dragStartY = e.clientY;
251
+ let t = this.toolbarEl.getBoundingClientRect();
252
+ this.dragOffsetX = e.clientX - t.left - t.width / 2, this.dragOffsetY = e.clientY - t.top - t.height / 2, this.host.style.transition = "none";
253
+ }
254
+ onPointerMove(e) {
255
+ if (!this.isDragging) return;
256
+ let t = e.clientX - this.dragStartX, n = e.clientY - this.dragStartY;
257
+ !this.wasDragged && Math.abs(t) < _ && Math.abs(n) < _ || (this.wasDragged || (this.wasDragged = !0, this.toolbarEl.setPointerCapture(this.dragPointerId), this.toolbarEl.classList.add("dragging")), this.posX = C((e.clientX - this.dragOffsetX) / window.innerWidth * 100, 2, 98), this.posY = C((e.clientY - this.dragOffsetY) / window.innerHeight * 100, 2, 98), this.applyPosition());
258
+ }
259
+ onPointerUp(e) {
260
+ this.isDragging && (this.isDragging = !1, this.toolbarEl.releasePointerCapture(e.pointerId), this.toolbarEl.classList.remove("dragging"), this.wasDragged && (this.host.style.transition = v, this.snapToEdge(), D(this.config.storageKey, this.posX, this.posY)));
261
+ }
262
+ snapToEdge() {
263
+ let e = this.getEdgeFromPosition(), t = x();
264
+ e === "right" ? this.posX = 100 - t : e === "bottom" ? this.posY = 100 - y : e === "top" ? this.posY = y : this.posX = t, this.applyPosition(), this.applyOrientation(e), this.panelOpen && this.positionPanel();
265
+ }
266
+ applyPosition() {
267
+ this.host && (this.host.style.left = `${this.posX}%`, this.host.style.top = `${this.posY}%`);
268
+ }
269
+ getEdgeFromPosition() {
270
+ let e = Math.atan2(this.posY - 50, this.posX - 50) * 180 / Math.PI;
271
+ return e >= -45 && e < 45 ? "right" : e >= 45 && e < 135 ? "bottom" : e >= -135 && e < -45 ? "top" : "left";
272
+ }
273
+ applyOrientation(e) {
274
+ if (!this.toolbarEl) return;
275
+ let t = e === "left" || e === "right";
276
+ this.toolbarEl.classList.toggle("vertical", t);
277
+ }
278
+ togglePanel() {
279
+ this.panelOpen ? this.closePanel() : this.openPanel();
280
+ }
281
+ openPanel() {
282
+ this.panelEl && (this.panelOpen = !0, this.positionPanel(), this.panelEl.classList.add("open"));
283
+ }
284
+ closePanel() {
285
+ this.panelEl && (this.panelOpen = !1, this.panelEl.classList.remove("open"), this.isRecording && this.stopRecording());
286
+ }
287
+ positionPanel() {
288
+ this.panelEl && (this.posX > 50 ? (this.panelEl.style.right = "calc(100% + 8px)", this.panelEl.style.left = "auto") : (this.panelEl.style.left = "calc(100% + 8px)", this.panelEl.style.right = "auto"), this.posY > 70 ? (this.panelEl.style.bottom = "-4px", this.panelEl.style.top = "auto") : (this.panelEl.style.top = "-4px", this.panelEl.style.bottom = "auto"));
289
+ }
290
+ toggleRecording() {
291
+ this.isRecording ? this.stopRecording() : this.startRecording();
292
+ }
293
+ startRecording() {
294
+ this.isRecording = !0, this.kbdEl.textContent = "Press keys…", this.kbdEl.classList.add("recording"), this.recordBtn.textContent = "Cancel", this.boundRecordKeyDown = (e) => {
295
+ if (e.preventDefault(), e.stopPropagation(), e.stopImmediatePropagation(), [
296
+ "Alt",
297
+ "Control",
298
+ "Shift",
299
+ "Meta"
300
+ ].includes(e.key)) return;
301
+ let t = m(e);
302
+ this.currentHotkey = t, this.stopRecording(), k(this.config.hotkeyStorageKey, t), this.hotkeyChangeCb?.(t);
303
+ }, document.addEventListener("keydown", this.boundRecordKeyDown, { capture: !0 });
304
+ }
305
+ stopRecording() {
306
+ this.isRecording = !1, this.boundRecordKeyDown &&= (document.removeEventListener("keydown", this.boundRecordKeyDown, { capture: !0 }), null), this.updateHotkeyDisplay(), this.recordBtn && (this.recordBtn.textContent = "Record"), this.kbdEl && this.kbdEl.classList.remove("recording");
307
+ }
308
+ updateHotkeyDisplay() {
309
+ this.kbdEl && (this.kbdEl.textContent = this.currentHotkey || "None");
310
+ }
311
+ }, P = new Set([
312
+ "header",
313
+ "nav",
314
+ "footer",
315
+ "aside",
316
+ "main",
317
+ "layout",
318
+ "sidebar"
319
+ ]), F = class {
320
+ config;
321
+ overlay = null;
322
+ callbacks = /* @__PURE__ */ new Set();
323
+ stateListeners = /* @__PURE__ */ new Set();
324
+ _isActive = !1;
325
+ prevCursor = "";
326
+ handleMouseMove = null;
327
+ handleClick = null;
328
+ handleKeyDown = null;
329
+ constructor(e) {
330
+ this.config = e;
331
+ }
332
+ get isActive() {
333
+ return this._isActive;
334
+ }
335
+ activate() {
336
+ this._isActive || (this._isActive = !0, this.stateListeners.forEach((e) => e(!0)), this.overlay = new f(this.config), this.overlay.mount(), this.prevCursor = document.body.style.cursor, document.body.style.cursor = "crosshair", this.handleMouseMove = (e) => {
337
+ let t = document.elementFromPoint(e.clientX, e.clientY);
338
+ if (!t || this.shouldIgnore(t)) {
339
+ this.overlay?.clearHighlight();
340
+ return;
341
+ }
342
+ let n = this.getVueComponent(t);
343
+ this.overlay?.highlight(t, this.getComponentLabelFromInstance(t, n));
344
+ }, this.handleClick = (e) => {
345
+ e.preventDefault(), e.stopPropagation(), e.stopImmediatePropagation();
346
+ let t = document.elementFromPoint(e.clientX, e.clientY);
347
+ if (!t || this.shouldIgnore(t)) return;
348
+ let n = t.outerHTML;
349
+ this.config.maxHtmlLength > 0 && n.length > this.config.maxHtmlLength && (n = n.slice(0, this.config.maxHtmlLength) + "<!-- truncated -->");
350
+ let r = {
351
+ element: t,
352
+ html: n,
353
+ componentStack: this.getComponentStack(t),
354
+ selector: this.generateSelector(t)
355
+ };
356
+ this.callbacks.forEach((e) => e(r)), this.deactivate();
357
+ }, this.handleKeyDown = (e) => {
358
+ e.key === "Escape" && (e.preventDefault(), this.deactivate());
359
+ }, document.addEventListener("mousemove", this.handleMouseMove, { capture: !0 }), document.addEventListener("click", this.handleClick, { capture: !0 }), document.addEventListener("keydown", this.handleKeyDown, { capture: !0 }));
360
+ }
361
+ deactivate() {
362
+ this._isActive && (this._isActive = !1, this.stateListeners.forEach((e) => e(!1)), this.handleMouseMove && document.removeEventListener("mousemove", this.handleMouseMove, { capture: !0 }), this.handleClick && document.removeEventListener("click", this.handleClick, { capture: !0 }), this.handleKeyDown && document.removeEventListener("keydown", this.handleKeyDown, { capture: !0 }), this.handleMouseMove = null, this.handleClick = null, this.handleKeyDown = null, this.overlay?.destroy(), this.overlay = null, document.body.style.cursor = this.prevCursor);
363
+ }
364
+ onGrab(e) {
365
+ return this.callbacks.add(e), () => this.callbacks.delete(e);
366
+ }
367
+ onStateChange(e) {
368
+ return this.stateListeners.add(e), () => this.stateListeners.delete(e);
369
+ }
370
+ toggle() {
371
+ this._isActive ? this.deactivate() : this.activate();
372
+ }
373
+ destroy() {
374
+ this.deactivate(), this.callbacks.clear(), this.stateListeners.clear();
375
+ }
376
+ updateConfig(e) {
377
+ this.config = c(this.config, e);
378
+ }
379
+ shouldIgnore(e) {
380
+ if (e.closest("#vue-grab-overlay-host, #vue-grab-fab-host")) return !0;
381
+ let t = e.tagName.toLowerCase();
382
+ if (this.config.filter.ignoreTags.includes(t)) return !0;
383
+ for (let t of this.config.filter.ignoreSelectors) try {
384
+ if (e.matches(t)) return !0;
385
+ } catch {}
386
+ if (this.config.filter.skipCommonComponents) {
387
+ let t = this.getVueComponent(e);
388
+ if (t) {
389
+ let e = t.type.name || t.type.__name || "";
390
+ if (P.has(e.toLowerCase())) return !0;
391
+ }
392
+ }
393
+ return !1;
394
+ }
395
+ getVueComponent(e) {
396
+ let t = e;
397
+ for (; t;) {
398
+ let e = t.__vueParentComponent || t.__vue_app__?._instance;
399
+ if (e) return e;
400
+ t = t.parentElement;
401
+ }
402
+ return null;
403
+ }
404
+ getComponentLabelFromInstance(e, t) {
405
+ if (t) {
406
+ let e = t.type?.name || t.type?.__name;
407
+ if (e) return `<${e}>`;
408
+ }
409
+ return `<${e.tagName.toLowerCase()}>`;
410
+ }
411
+ getComponentStack(e) {
412
+ let t = [], n = e;
413
+ for (; n;) {
414
+ let e = n.__vueParentComponent || n.__vue_app__?._instance;
415
+ if (e) {
416
+ let n = e.type?.name || e.type?.__name || "Anonymous", r = e.type?.__file, i = { name: n };
417
+ r && (i.filePath = r), t.push(i);
418
+ }
419
+ n = n.parentElement;
420
+ }
421
+ return t;
422
+ }
423
+ generateSelector(e) {
424
+ if (e.id) return `#${CSS.escape(e.id)}`;
425
+ let t = [], n = e;
426
+ for (; n && n !== document.body && n !== document.documentElement;) {
427
+ let e = n.tagName.toLowerCase();
428
+ if (n.id) {
429
+ t.unshift(`#${CSS.escape(n.id)}`);
430
+ break;
431
+ }
432
+ if (n.className && typeof n.className == "string") {
433
+ let t = n.className.trim().split(/\s+/).filter(Boolean);
434
+ t.length > 0 && (e += `.${t.slice(0, 2).map((e) => CSS.escape(e)).join(".")}`);
435
+ }
436
+ t.unshift(e), n = n.parentElement;
437
+ }
438
+ return t.join(" > ");
439
+ }
440
+ };
441
+ //#endregion
442
+ //#region src/session.ts
443
+ function I(e) {
444
+ let t = new F(e), n = new h(), r = null;
445
+ if (e.floatingButton.enabled) {
446
+ let i = new N(e.floatingButton);
447
+ r = i;
448
+ let a = i.getCurrentHotkey() || "Alt+Shift+G";
449
+ n.register(a, () => t.toggle()), i.setHighlightColor(e.highlightColor), i.setCurrentHotkey(a), i.onToggle(() => t.toggle()), i.onHotkeyChange((e) => {
450
+ n.destroy(), n.register(e, () => t.toggle()), i.setCurrentHotkey(e);
451
+ }), t.onStateChange((e) => i.setActive(e)), i.mount();
452
+ } else n.register(o, () => t.toggle());
453
+ return {
454
+ engine: t,
455
+ hotkeys: n,
456
+ fab: r,
457
+ destroy() {
458
+ t.destroy(), n.destroy(), r?.destroy();
459
+ }
460
+ };
461
+ }
462
+ //#endregion
463
+ //#region src/composables/index.ts
464
+ function L() {
465
+ let i = e(l, { ...s }), a = r(!1), o = r(null), c = I(i), { engine: u } = c, d = u.onGrab((e) => {
466
+ o.value = e;
467
+ }), f = u.onStateChange((e) => {
468
+ a.value = e;
469
+ });
470
+ return t(() => {
471
+ d(), f(), c.destroy();
472
+ }), {
473
+ config: i,
474
+ isActive: n(a),
475
+ lastResult: n(o),
476
+ activate: () => u.activate(),
477
+ deactivate: () => u.deactivate(),
478
+ toggle: () => u.toggle()
479
+ };
480
+ }
481
+ //#endregion
482
+ //#region src/init.ts
483
+ function R(e = {}) {
484
+ let t = I(c(s, e));
485
+ return {
486
+ activate: () => t.engine.activate(),
487
+ deactivate: () => t.engine.deactivate(),
488
+ onGrab: (e) => t.engine.onGrab(e),
489
+ destroy: () => t.destroy()
490
+ };
491
+ }
492
+ //#endregion
493
+ export { g as FAB_HOST_ID, N as FloatingButton, l as VUE_GRAB_CONFIG_KEY, u as createVueGrab, R as init, L as useGrab };
@@ -0,0 +1,197 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`vue`)):typeof define==`function`&&define.amd?define([`exports`,`vue`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.VueGrab={},e.Vue))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=`#4f46e5`,r=`#ffffff`,i=`Alt+Shift+G`,a={highlightColor:n,labelTextColor:r,showTagHint:!0,maxHtmlLength:1e4,filter:{ignoreSelectors:[],ignoreTags:[],skipCommonComponents:!1},floatingButton:{enabled:!1,initialPosition:`top-center`,storageKey:`vue-grab-fab-pos`,hotkeyStorageKey:`vue-grab-hotkey`}};function o(e,t){let{filter:n,floatingButton:r,...i}=t;return{...e,...i,filter:{...e.filter,...n},floatingButton:{...e.floatingButton,...r}}}var s=Symbol(`vue-grab-config`);function c(e={}){let t=o(a,e);return{install(e){e.provide(s,t)}}}var l=`vue-grab-overlay-host`,u=class{host=null;shadowRoot=null;highlightBox=null;labelEl=null;config;constructor(e){this.config=e}mount(){if(this.host)return;this.host=document.createElement(`div`),this.host.id=l,this.host.style.cssText=`position:fixed;top:0;left:0;width:0;height:0;z-index:2147483647;pointer-events:none;`,document.body.appendChild(this.host),this.shadowRoot=this.host.attachShadow({mode:`open`}),this.host.style.setProperty(`--grab-color`,this.config.highlightColor),this.host.style.setProperty(`--grab-label-color`,this.config.labelTextColor);let e=document.createElement(`style`);e.textContent=`
2
+ .grab-highlight {
3
+ position: fixed;
4
+ pointer-events: none;
5
+ border: 2px solid var(--grab-color);
6
+ background: color-mix(in srgb, var(--grab-color) 12%, transparent);
7
+ border-radius: 2px;
8
+ transition: all 0.05s ease-out;
9
+ display: none;
10
+ box-sizing: border-box;
11
+ }
12
+ .grab-label {
13
+ position: fixed;
14
+ pointer-events: none;
15
+ background: var(--grab-color);
16
+ color: var(--grab-label-color);
17
+ font-size: 11px;
18
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
19
+ padding: 2px 6px;
20
+ border-radius: 2px;
21
+ white-space: nowrap;
22
+ display: none;
23
+ line-height: 1.4;
24
+ }
25
+ `,this.shadowRoot.appendChild(e),this.highlightBox=document.createElement(`div`),this.highlightBox.className=`grab-highlight`,this.shadowRoot.appendChild(this.highlightBox),this.labelEl=document.createElement(`div`),this.labelEl.className=`grab-label`,this.shadowRoot.appendChild(this.labelEl)}highlight(e,t){if(!this.highlightBox||!this.labelEl)return;let n=e.getBoundingClientRect();this.highlightBox.style.top=`${n.top}px`,this.highlightBox.style.left=`${n.left}px`,this.highlightBox.style.width=`${n.width}px`,this.highlightBox.style.height=`${n.height}px`,this.highlightBox.style.display=`block`,this.config.showTagHint&&t?(this.labelEl.textContent=t,n.top>24?this.labelEl.style.top=`${n.top-20-4}px`:this.labelEl.style.top=`${n.bottom+4}px`,this.labelEl.style.left=`${n.left}px`,this.labelEl.style.display=`block`):this.labelEl.style.display=`none`}clearHighlight(){this.highlightBox&&(this.highlightBox.style.display=`none`),this.labelEl&&(this.labelEl.style.display=`none`)}destroy(){this.host&&(this.host.remove(),this.host=null,this.shadowRoot=null,this.highlightBox=null,this.labelEl=null)}};function d(e){let t=e.split(`+`).map(e=>e.trim().toLowerCase());return{key:t[t.length-1],alt:t.includes(`alt`),ctrl:t.includes(`ctrl`)||t.includes(`control`),shift:t.includes(`shift`),meta:t.includes(`meta`)||t.includes(`cmd`)||t.includes(`command`)}}function f(e){let t=[];e.ctrlKey&&t.push(`Ctrl`),e.altKey&&t.push(`Alt`),e.shiftKey&&t.push(`Shift`),e.metaKey&&t.push(`Meta`);let n=e.key.length===1?e.key.toUpperCase():e.key;return t.push(n),t.join(`+`)}var p=class{cleanups=[];register(e,t){let n=d(e),r=e=>{e.key.toLowerCase()===n.key&&e.altKey===n.alt&&e.ctrlKey===n.ctrl&&e.shiftKey===n.shift&&e.metaKey===n.meta&&(e.preventDefault(),t())};document.addEventListener(`keydown`,r,{capture:!0}),this.cleanups.push(()=>document.removeEventListener(`keydown`,r,{capture:!0}))}destroy(){this.cleanups.forEach(e=>e()),this.cleanups=[]}},m=`vue-grab-fab-host`,h=3,g=`left 0.3s ease, top 0.3s ease`,_=3,v=5;function y(){return _*window.innerHeight/window.innerWidth}var b={"bottom-right":{x:97,y:85},"bottom-left":{x:3,y:85},"top-right":{x:97,y:15},"top-left":{x:3,y:15},"top-center":{x:50,y:3}};function x(e,t,n){return Math.min(Math.max(e,t),n)}function S(e,t){if(!e)return null;try{let n=localStorage.getItem(e);return n?t(n):null}catch{return null}}function C(e,t){if(e)try{localStorage.setItem(e,t)}catch{}}function w(e){return S(e,e=>{let{x:t,y:n}=JSON.parse(e);return typeof t==`number`&&typeof n==`number`?{x:t,y:n}:null})}function T(e,t,n){C(e,JSON.stringify({x:t,y:n}))}function E(e){return S(e,e=>typeof e==`string`?e:null)}function D(e,t){C(e,t)}var O=`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="22" y1="12" x2="18" y2="12"/><line x1="6" y1="12" x2="2" y2="12"/><line x1="12" y1="6" x2="12" y2="2"/><line x1="12" y1="22" x2="12" y2="18"/><line x1="12" y1="15" x2="12" y2="9"/><line x1="9" y1="12" x2="15" y2="12"/></svg>`,k=`<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.32 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`,A=`
26
+ :host {
27
+ all: initial;
28
+ }
29
+ .toolbar {
30
+ display: inline-flex;
31
+ align-items: center;
32
+ height: 36px;
33
+ padding: 0 4px;
34
+ gap: 2px;
35
+ border-radius: 8px;
36
+ border: 1px solid rgba(255,255,255,0.1);
37
+ background: rgba(30,30,30,0.85);
38
+ backdrop-filter: blur(8px);
39
+ -webkit-backdrop-filter: blur(8px);
40
+ box-shadow: 0 2px 10px rgba(0,0,0,0.35);
41
+ cursor: grab;
42
+ user-select: none;
43
+ touch-action: none;
44
+ position: relative;
45
+ }
46
+ .toolbar.dragging {
47
+ cursor: grabbing;
48
+ box-shadow: 0 4px 16px rgba(0,0,0,0.5);
49
+ }
50
+ .toolbar-btn {
51
+ width: 28px;
52
+ height: 28px;
53
+ border-radius: 6px;
54
+ border: none;
55
+ background: transparent;
56
+ color: #a0a0a0;
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ cursor: pointer;
61
+ transition: color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
62
+ }
63
+ .toolbar-btn:hover {
64
+ color: #e0e0e0;
65
+ background: rgba(255,255,255,0.08);
66
+ }
67
+ .grab-btn.active {
68
+ color: var(--grab-color, #4f46e5);
69
+ box-shadow: inset 0 0 0 1.5px var(--grab-color, #4f46e5);
70
+ background: color-mix(in srgb, var(--grab-color, #4f46e5) 12%, transparent);
71
+ }
72
+ .toolbar-divider {
73
+ width: 1px;
74
+ height: 18px;
75
+ background: rgba(255,255,255,0.12);
76
+ margin: 0 2px;
77
+ }
78
+
79
+ .panel {
80
+ position: absolute;
81
+ width: 240px;
82
+ background: rgba(25,25,25,0.94);
83
+ backdrop-filter: blur(12px);
84
+ -webkit-backdrop-filter: blur(12px);
85
+ border: 1px solid rgba(255,255,255,0.1);
86
+ border-radius: 12px;
87
+ color: #e0e0e0;
88
+ font-family: system-ui, -apple-system, sans-serif;
89
+ font-size: 13px;
90
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4);
91
+ padding: 14px;
92
+ display: none;
93
+ z-index: 1;
94
+ }
95
+ .panel.open {
96
+ display: block;
97
+ }
98
+ .panel-header {
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: space-between;
102
+ margin-bottom: 12px;
103
+ font-weight: 600;
104
+ font-size: 13px;
105
+ color: #fff;
106
+ }
107
+ .panel-close {
108
+ background: none;
109
+ border: none;
110
+ color: #888;
111
+ cursor: pointer;
112
+ font-size: 16px;
113
+ line-height: 1;
114
+ padding: 0 2px;
115
+ }
116
+ .panel-close:hover {
117
+ color: #fff;
118
+ }
119
+ .panel-label {
120
+ font-size: 11px;
121
+ color: #888;
122
+ margin-bottom: 6px;
123
+ text-transform: uppercase;
124
+ letter-spacing: 0.5px;
125
+ }
126
+ .hotkey-row {
127
+ display: flex;
128
+ align-items: center;
129
+ gap: 8px;
130
+ }
131
+ kbd {
132
+ display: inline-flex;
133
+ align-items: center;
134
+ background: rgba(255,255,255,0.08);
135
+ border: 1px solid rgba(255,255,255,0.15);
136
+ border-radius: 6px;
137
+ padding: 4px 10px;
138
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
139
+ font-size: 12px;
140
+ color: #ddd;
141
+ min-width: 80px;
142
+ justify-content: center;
143
+ flex: 1;
144
+ }
145
+ kbd.recording {
146
+ border-color: var(--grab-color, #4f46e5);
147
+ box-shadow: 0 0 0 1px var(--grab-color, #4f46e5);
148
+ animation: pulse 1.5s ease-in-out infinite;
149
+ }
150
+ @keyframes pulse {
151
+ 0%, 100% { opacity: 1; }
152
+ 50% { opacity: 0.6; }
153
+ }
154
+ .record-btn {
155
+ background: rgba(255,255,255,0.08);
156
+ border: 1px solid rgba(255,255,255,0.15);
157
+ border-radius: 6px;
158
+ color: #ccc;
159
+ padding: 4px 10px;
160
+ font-size: 12px;
161
+ cursor: pointer;
162
+ white-space: nowrap;
163
+ font-family: inherit;
164
+ }
165
+ .record-btn:hover {
166
+ background: rgba(255,255,255,0.14);
167
+ color: #fff;
168
+ }
169
+ .panel-hint {
170
+ margin-top: 12px;
171
+ font-size: 11px;
172
+ color: #555;
173
+ text-align: center;
174
+ }
175
+ .toolbar.vertical {
176
+ flex-direction: column;
177
+ height: auto;
178
+ width: 36px;
179
+ padding: 4px 0;
180
+ }
181
+ .toolbar.vertical .toolbar-divider {
182
+ width: 18px;
183
+ height: 1px;
184
+ margin: 2px 0;
185
+ }
186
+ `,j=class{host=null;shadowRoot=null;toolbarEl=null;btnEl=null;gearEl=null;panelEl=null;kbdEl=null;recordBtn=null;config;posX=97;posY=85;isDragging=!1;wasDragged=!1;dragPointerId=-1;dragStartX=0;dragStartY=0;dragOffsetX=0;dragOffsetY=0;panelOpen=!1;isRecording=!1;currentHotkey=``;toggleCb=null;hotkeyChangeCb=null;boundPointerDown=null;boundPointerMove=null;boundPointerUp=null;boundDocClick=null;boundDocKeyDown=null;boundRecordKeyDown=null;constructor(e){this.config=e;let t=b[e.initialPosition]??b[`bottom-right`],n=w(e.storageKey);n?(this.posX=n.x,this.posY=n.y):(this.posX=t.x,this.posY=t.y,this.posX<=v?this.posX=y():this.posX>=100-v&&(this.posX=100-y())),this.currentHotkey=E(e.hotkeyStorageKey)??``}getCurrentHotkey(){return this.currentHotkey}mount(){if(this.host)return;this.host=document.createElement(`div`),this.host.id=m,this.host.style.cssText=`position:fixed;z-index:2147483646;pointer-events:auto;transform:translate(-50%,-50%);transition:${g};`,this.applyPosition(),document.body.appendChild(this.host),this.shadowRoot=this.host.attachShadow({mode:`open`});let e=document.createElement(`style`);e.textContent=A,this.shadowRoot.appendChild(e),this.toolbarEl=document.createElement(`div`),this.toolbarEl.className=`toolbar`,this.btnEl=document.createElement(`div`),this.btnEl.className=`toolbar-btn grab-btn`,this.btnEl.innerHTML=O,this.toolbarEl.appendChild(this.btnEl);let t=document.createElement(`div`);t.className=`toolbar-divider`,this.toolbarEl.appendChild(t),this.gearEl=document.createElement(`div`),this.gearEl.className=`toolbar-btn gear-btn`,this.gearEl.innerHTML=k,this.toolbarEl.appendChild(this.gearEl),this.panelEl=document.createElement(`div`),this.panelEl.className=`panel`,this.panelEl.innerHTML=`
187
+ <div class="panel-header">
188
+ <span>Settings</span>
189
+ <button class="panel-close" data-action="close">&times;</button>
190
+ </div>
191
+ <div class="panel-label">Hotkey</div>
192
+ <div class="hotkey-row">
193
+ <kbd></kbd>
194
+ <button class="record-btn">Record</button>
195
+ </div>
196
+ <div class="panel-hint">Drag toolbar to reposition</div>
197
+ `,this.toolbarEl.appendChild(this.panelEl),this.shadowRoot.appendChild(this.toolbarEl),this.kbdEl=this.panelEl.querySelector(`kbd`),this.recordBtn=this.panelEl.querySelector(`.record-btn`),this.updateHotkeyDisplay(),this.applyOrientation(this.getEdgeFromPosition()),this.boundPointerDown=this.onPointerDown.bind(this),this.boundPointerMove=this.onPointerMove.bind(this),this.boundPointerUp=this.onPointerUp.bind(this),this.toolbarEl.addEventListener(`pointerdown`,this.boundPointerDown),this.toolbarEl.addEventListener(`pointermove`,this.boundPointerMove),this.toolbarEl.addEventListener(`pointerup`,this.boundPointerUp),this.btnEl.addEventListener(`click`,e=>{if(e.stopPropagation(),!this.wasDragged){if(this.panelOpen){this.closePanel();return}this.toggleCb?.()}}),this.gearEl.addEventListener(`click`,e=>{e.stopPropagation(),!this.wasDragged&&this.togglePanel()}),this.panelEl.querySelector(`[data-action=close]`).addEventListener(`click`,e=>{e.stopPropagation(),this.closePanel()}),this.recordBtn.addEventListener(`click`,e=>{e.stopPropagation(),this.toggleRecording()}),this.boundDocClick=e=>{this.panelOpen&&(e.composedPath().includes(this.host)||this.closePanel())},document.addEventListener(`click`,this.boundDocClick,{capture:!0}),this.boundDocKeyDown=e=>{e.key===`Escape`&&(this.isRecording?(this.stopRecording(),e.preventDefault(),e.stopPropagation()):this.panelOpen&&(this.closePanel(),e.preventDefault(),e.stopPropagation()))},document.addEventListener(`keydown`,this.boundDocKeyDown,{capture:!0})}destroy(){this.boundDocClick&&document.removeEventListener(`click`,this.boundDocClick,{capture:!0}),this.boundDocKeyDown&&document.removeEventListener(`keydown`,this.boundDocKeyDown,{capture:!0}),this.boundRecordKeyDown&&document.removeEventListener(`keydown`,this.boundRecordKeyDown,{capture:!0}),this.host&&(this.host.remove(),this.host=null,this.shadowRoot=null,this.toolbarEl=null,this.btnEl=null,this.gearEl=null,this.panelEl=null,this.kbdEl=null,this.recordBtn=null)}setActive(e){this.btnEl&&(e?this.btnEl.classList.add(`active`):this.btnEl.classList.remove(`active`))}setHighlightColor(e){this.host?.style.setProperty(`--grab-color`,e)}setCurrentHotkey(e){this.currentHotkey=e,this.updateHotkeyDisplay()}onToggle(e){this.toggleCb=e}onHotkeyChange(e){this.hotkeyChangeCb=e}onPointerDown(e){if(e.button!==0)return;this.isDragging=!0,this.wasDragged=!1,this.dragPointerId=e.pointerId,this.dragStartX=e.clientX,this.dragStartY=e.clientY;let t=this.toolbarEl.getBoundingClientRect();this.dragOffsetX=e.clientX-t.left-t.width/2,this.dragOffsetY=e.clientY-t.top-t.height/2,this.host.style.transition=`none`}onPointerMove(e){if(!this.isDragging)return;let t=e.clientX-this.dragStartX,n=e.clientY-this.dragStartY;!this.wasDragged&&Math.abs(t)<h&&Math.abs(n)<h||(this.wasDragged||(this.wasDragged=!0,this.toolbarEl.setPointerCapture(this.dragPointerId),this.toolbarEl.classList.add(`dragging`)),this.posX=x((e.clientX-this.dragOffsetX)/window.innerWidth*100,2,98),this.posY=x((e.clientY-this.dragOffsetY)/window.innerHeight*100,2,98),this.applyPosition())}onPointerUp(e){this.isDragging&&(this.isDragging=!1,this.toolbarEl.releasePointerCapture(e.pointerId),this.toolbarEl.classList.remove(`dragging`),this.wasDragged&&(this.host.style.transition=g,this.snapToEdge(),T(this.config.storageKey,this.posX,this.posY)))}snapToEdge(){let e=this.getEdgeFromPosition(),t=y();e===`right`?this.posX=100-t:e===`bottom`?this.posY=100-_:e===`top`?this.posY=_:this.posX=t,this.applyPosition(),this.applyOrientation(e),this.panelOpen&&this.positionPanel()}applyPosition(){this.host&&(this.host.style.left=`${this.posX}%`,this.host.style.top=`${this.posY}%`)}getEdgeFromPosition(){let e=Math.atan2(this.posY-50,this.posX-50)*180/Math.PI;return e>=-45&&e<45?`right`:e>=45&&e<135?`bottom`:e>=-135&&e<-45?`top`:`left`}applyOrientation(e){if(!this.toolbarEl)return;let t=e===`left`||e===`right`;this.toolbarEl.classList.toggle(`vertical`,t)}togglePanel(){this.panelOpen?this.closePanel():this.openPanel()}openPanel(){this.panelEl&&(this.panelOpen=!0,this.positionPanel(),this.panelEl.classList.add(`open`))}closePanel(){this.panelEl&&(this.panelOpen=!1,this.panelEl.classList.remove(`open`),this.isRecording&&this.stopRecording())}positionPanel(){this.panelEl&&(this.posX>50?(this.panelEl.style.right=`calc(100% + 8px)`,this.panelEl.style.left=`auto`):(this.panelEl.style.left=`calc(100% + 8px)`,this.panelEl.style.right=`auto`),this.posY>70?(this.panelEl.style.bottom=`-4px`,this.panelEl.style.top=`auto`):(this.panelEl.style.top=`-4px`,this.panelEl.style.bottom=`auto`))}toggleRecording(){this.isRecording?this.stopRecording():this.startRecording()}startRecording(){this.isRecording=!0,this.kbdEl.textContent=`Press keys…`,this.kbdEl.classList.add(`recording`),this.recordBtn.textContent=`Cancel`,this.boundRecordKeyDown=e=>{if(e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation(),[`Alt`,`Control`,`Shift`,`Meta`].includes(e.key))return;let t=f(e);this.currentHotkey=t,this.stopRecording(),D(this.config.hotkeyStorageKey,t),this.hotkeyChangeCb?.(t)},document.addEventListener(`keydown`,this.boundRecordKeyDown,{capture:!0})}stopRecording(){this.isRecording=!1,this.boundRecordKeyDown&&=(document.removeEventListener(`keydown`,this.boundRecordKeyDown,{capture:!0}),null),this.updateHotkeyDisplay(),this.recordBtn&&(this.recordBtn.textContent=`Record`),this.kbdEl&&this.kbdEl.classList.remove(`recording`)}updateHotkeyDisplay(){this.kbdEl&&(this.kbdEl.textContent=this.currentHotkey||`None`)}},M=new Set([`header`,`nav`,`footer`,`aside`,`main`,`layout`,`sidebar`]),N=class{config;overlay=null;callbacks=new Set;stateListeners=new Set;_isActive=!1;prevCursor=``;handleMouseMove=null;handleClick=null;handleKeyDown=null;constructor(e){this.config=e}get isActive(){return this._isActive}activate(){this._isActive||(this._isActive=!0,this.stateListeners.forEach(e=>e(!0)),this.overlay=new u(this.config),this.overlay.mount(),this.prevCursor=document.body.style.cursor,document.body.style.cursor=`crosshair`,this.handleMouseMove=e=>{let t=document.elementFromPoint(e.clientX,e.clientY);if(!t||this.shouldIgnore(t)){this.overlay?.clearHighlight();return}let n=this.getVueComponent(t);this.overlay?.highlight(t,this.getComponentLabelFromInstance(t,n))},this.handleClick=e=>{e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation();let t=document.elementFromPoint(e.clientX,e.clientY);if(!t||this.shouldIgnore(t))return;let n=t.outerHTML;this.config.maxHtmlLength>0&&n.length>this.config.maxHtmlLength&&(n=n.slice(0,this.config.maxHtmlLength)+`<!-- truncated -->`);let r={element:t,html:n,componentStack:this.getComponentStack(t),selector:this.generateSelector(t)};this.callbacks.forEach(e=>e(r)),this.deactivate()},this.handleKeyDown=e=>{e.key===`Escape`&&(e.preventDefault(),this.deactivate())},document.addEventListener(`mousemove`,this.handleMouseMove,{capture:!0}),document.addEventListener(`click`,this.handleClick,{capture:!0}),document.addEventListener(`keydown`,this.handleKeyDown,{capture:!0}))}deactivate(){this._isActive&&(this._isActive=!1,this.stateListeners.forEach(e=>e(!1)),this.handleMouseMove&&document.removeEventListener(`mousemove`,this.handleMouseMove,{capture:!0}),this.handleClick&&document.removeEventListener(`click`,this.handleClick,{capture:!0}),this.handleKeyDown&&document.removeEventListener(`keydown`,this.handleKeyDown,{capture:!0}),this.handleMouseMove=null,this.handleClick=null,this.handleKeyDown=null,this.overlay?.destroy(),this.overlay=null,document.body.style.cursor=this.prevCursor)}onGrab(e){return this.callbacks.add(e),()=>this.callbacks.delete(e)}onStateChange(e){return this.stateListeners.add(e),()=>this.stateListeners.delete(e)}toggle(){this._isActive?this.deactivate():this.activate()}destroy(){this.deactivate(),this.callbacks.clear(),this.stateListeners.clear()}updateConfig(e){this.config=o(this.config,e)}shouldIgnore(e){if(e.closest(`#vue-grab-overlay-host, #vue-grab-fab-host`))return!0;let t=e.tagName.toLowerCase();if(this.config.filter.ignoreTags.includes(t))return!0;for(let t of this.config.filter.ignoreSelectors)try{if(e.matches(t))return!0}catch{}if(this.config.filter.skipCommonComponents){let t=this.getVueComponent(e);if(t){let e=t.type.name||t.type.__name||``;if(M.has(e.toLowerCase()))return!0}}return!1}getVueComponent(e){let t=e;for(;t;){let e=t.__vueParentComponent||t.__vue_app__?._instance;if(e)return e;t=t.parentElement}return null}getComponentLabelFromInstance(e,t){if(t){let e=t.type?.name||t.type?.__name;if(e)return`<${e}>`}return`<${e.tagName.toLowerCase()}>`}getComponentStack(e){let t=[],n=e;for(;n;){let e=n.__vueParentComponent||n.__vue_app__?._instance;if(e){let n=e.type?.name||e.type?.__name||`Anonymous`,r=e.type?.__file,i={name:n};r&&(i.filePath=r),t.push(i)}n=n.parentElement}return t}generateSelector(e){if(e.id)return`#${CSS.escape(e.id)}`;let t=[],n=e;for(;n&&n!==document.body&&n!==document.documentElement;){let e=n.tagName.toLowerCase();if(n.id){t.unshift(`#${CSS.escape(n.id)}`);break}if(n.className&&typeof n.className==`string`){let t=n.className.trim().split(/\s+/).filter(Boolean);t.length>0&&(e+=`.${t.slice(0,2).map(e=>CSS.escape(e)).join(`.`)}`)}t.unshift(e),n=n.parentElement}return t.join(` > `)}};function P(e){let t=new N(e),n=new p,r=null;if(e.floatingButton.enabled){let i=new j(e.floatingButton);r=i;let a=i.getCurrentHotkey()||`Alt+Shift+G`;n.register(a,()=>t.toggle()),i.setHighlightColor(e.highlightColor),i.setCurrentHotkey(a),i.onToggle(()=>t.toggle()),i.onHotkeyChange(e=>{n.destroy(),n.register(e,()=>t.toggle()),i.setCurrentHotkey(e)}),t.onStateChange(e=>i.setActive(e)),i.mount()}else n.register(i,()=>t.toggle());return{engine:t,hotkeys:n,fab:r,destroy(){t.destroy(),n.destroy(),r?.destroy()}}}function F(){let e=(0,t.inject)(s,{...a}),n=(0,t.ref)(!1),r=(0,t.ref)(null),i=P(e),{engine:o}=i,c=o.onGrab(e=>{r.value=e}),l=o.onStateChange(e=>{n.value=e});return(0,t.onUnmounted)(()=>{c(),l(),i.destroy()}),{config:e,isActive:(0,t.readonly)(n),lastResult:(0,t.readonly)(r),activate:()=>o.activate(),deactivate:()=>o.deactivate(),toggle:()=>o.toggle()}}function I(e={}){let t=P(o(a,e));return{activate:()=>t.engine.activate(),deactivate:()=>t.engine.deactivate(),onGrab:e=>t.engine.onGrab(e),destroy:()=>t.destroy()}}e.FAB_HOST_ID=m,e.FloatingButton=j,e.VUE_GRAB_CONFIG_KEY=s,e.createVueGrab=c,e.init=I,e.useGrab=F});
package/dist/init.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { GrabConfig, GrabResult } from '@sakana-y/vue-grab-shared';
2
+ /**
3
+ * Standalone initialization for non-Vue contexts (e.g., script tag usage).
4
+ */
5
+ export declare function init(options?: Partial<GrabConfig>): {
6
+ activate: () => void;
7
+ deactivate: () => void;
8
+ onGrab: (cb: (result: GrabResult) => void) => () => void;
9
+ destroy: () => void;
10
+ };
11
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAIxE;;GAEG;AACH,wBAAgB,IAAI,CAAC,OAAO,GAAE,OAAO,CAAC,UAAU,CAAM;;;iBAOrC,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI;;EAG5C"}
@@ -0,0 +1,16 @@
1
+ import { GrabConfig } from '@sakana-y/vue-grab-shared';
2
+ declare const OVERLAY_HOST_ID = "vue-grab-overlay-host";
3
+ export declare class GrabOverlay {
4
+ private host;
5
+ private shadowRoot;
6
+ private highlightBox;
7
+ private labelEl;
8
+ private config;
9
+ constructor(config: Pick<GrabConfig, "highlightColor" | "labelTextColor" | "showTagHint">);
10
+ mount(): void;
11
+ highlight(el: Element, label?: string): void;
12
+ clearHighlight(): void;
13
+ destroy(): void;
14
+ }
15
+ export { OVERLAY_HOST_ID };
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/overlay/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,QAAA,MAAM,eAAe,0BAA0B,CAAC;AAEhD,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAA4B;IACxC,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,MAAM,CAAwE;gBAE1E,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,aAAa,CAAC;IAIzF,KAAK,IAAI,IAAI;IAoDb,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IA2B5C,cAAc,IAAI,IAAI;IAKtB,OAAO,IAAI,IAAI;CAShB;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { App, InjectionKey } from 'vue';
2
+ import { GrabConfig } from '@sakana-y/vue-grab-shared';
3
+ export declare const VUE_GRAB_CONFIG_KEY: InjectionKey<GrabConfig>;
4
+ export declare function createVueGrab(options?: Partial<GrabConfig>): {
5
+ install(app: App): void;
6
+ };
7
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAG5D,eAAO,MAAM,mBAAmB,EAAE,YAAY,CAAC,UAAU,CAA6B,CAAC;AAEvF,wBAAgB,aAAa,CAAC,OAAO,GAAE,OAAO,CAAC,UAAU,CAAM;iBAI9C,GAAG;EAInB"}
@@ -0,0 +1,17 @@
1
+ import { GrabConfig } from '@sakana-y/vue-grab-shared';
2
+ import { GrabEngine } from './core';
3
+ import { HotkeyManager } from './hotkeys';
4
+ import { FloatingButton } from './floating-button';
5
+ export interface GrabSession {
6
+ engine: GrabEngine;
7
+ hotkeys: HotkeyManager;
8
+ fab: FloatingButton | null;
9
+ destroy: () => void;
10
+ }
11
+ /**
12
+ * Creates a GrabEngine + HotkeyManager pair with the default hotkey wired up.
13
+ * Optionally creates a FloatingButton when config.floatingButton.enabled is true.
14
+ * Shared setup used by both the Vue composable and the standalone `init()`.
15
+ */
16
+ export declare function createGrabSession(config: GrabConfig): GrabSession;
17
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,aAAa,CAAC;IACvB,GAAG,EAAE,cAAc,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,CAkCjE"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@sakana-y/vue-grab",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "type": "module",
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.mjs",
11
+ "types": "./dist/index.d.ts",
12
+ "unpkg": "./dist/index.umd.js",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/index.mjs",
16
+ "require": "./dist/index.cjs",
17
+ "types": "./dist/index.d.ts"
18
+ }
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "dependencies": {
24
+ "@sakana-y/vue-grab-shared": "0.0.1"
25
+ },
26
+ "devDependencies": {
27
+ "@playwright/test": "^1.50.0",
28
+ "@vitejs/plugin-vue": "^6.0.0",
29
+ "@vitest/browser": "^4.1.0",
30
+ "@vitest/browser-playwright": "^4.1.0",
31
+ "@vue/test-utils": "^2.4.6",
32
+ "typescript": "~5.9.3",
33
+ "vite": "^8.0.0",
34
+ "vite-plugin-dts": "^4.5.0",
35
+ "vitest": "^4.1.0",
36
+ "vue": "^3.5.13"
37
+ },
38
+ "peerDependencies": {
39
+ "vue": "^3.5.0"
40
+ },
41
+ "scripts": {
42
+ "build": "vite build",
43
+ "test": "vitest run",
44
+ "typecheck": "tsc --noEmit"
45
+ }
46
+ }