angular-grab 0.1.0 → 0.1.2

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 (132) hide show
  1. package/README.md +215 -0
  2. package/examples/angular-19-app/.editorconfig +17 -0
  3. package/examples/angular-19-app/.vscode/extensions.json +4 -0
  4. package/examples/angular-19-app/.vscode/launch.json +20 -0
  5. package/examples/angular-19-app/.vscode/mcp.json +9 -0
  6. package/examples/angular-19-app/.vscode/tasks.json +42 -0
  7. package/examples/angular-19-app/README.md +59 -0
  8. package/examples/angular-19-app/angular.json +74 -0
  9. package/examples/angular-19-app/package.json +44 -0
  10. package/examples/angular-19-app/public/favicon.ico +0 -0
  11. package/examples/angular-19-app/src/app/app.config.ts +13 -0
  12. package/examples/angular-19-app/src/app/app.css +37 -0
  13. package/examples/angular-19-app/src/app/app.html +25 -0
  14. package/examples/angular-19-app/src/app/app.routes.ts +3 -0
  15. package/examples/angular-19-app/src/app/app.spec.ts +23 -0
  16. package/examples/angular-19-app/src/app/app.ts +12 -0
  17. package/examples/angular-19-app/src/app/button/button.component.ts +25 -0
  18. package/examples/angular-19-app/src/app/card/card.component.ts +33 -0
  19. package/examples/angular-19-app/src/app/header/header.component.ts +31 -0
  20. package/examples/angular-19-app/src/app/popover/popover.component.ts +133 -0
  21. package/examples/angular-19-app/src/index.html +13 -0
  22. package/examples/angular-19-app/src/main.ts +6 -0
  23. package/examples/angular-19-app/src/styles.css +1 -0
  24. package/examples/angular-19-app/tsconfig.app.json +15 -0
  25. package/examples/angular-19-app/tsconfig.json +33 -0
  26. package/examples/angular-19-app/tsconfig.spec.json +15 -0
  27. package/package.json +14 -111
  28. package/packages/angular-grab/package.json +96 -0
  29. package/packages/angular-grab/src/angular/__tests__/context-builder.test.ts +216 -0
  30. package/packages/angular-grab/src/angular/angular-grab.service.ts +62 -0
  31. package/packages/angular-grab/src/angular/index.ts +13 -0
  32. package/packages/angular-grab/src/angular/provide-angular-grab.ts +22 -0
  33. package/packages/angular-grab/src/angular/resolvers/component-resolver.ts +71 -0
  34. package/packages/angular-grab/src/angular/resolvers/context-builder.ts +86 -0
  35. package/packages/angular-grab/src/angular/resolvers/ng-utils.ts +14 -0
  36. package/packages/angular-grab/src/angular/resolvers/source-resolver.ts +61 -0
  37. package/packages/angular-grab/src/builder/__tests__/builder.test.ts +72 -0
  38. package/packages/angular-grab/src/builder/builders/application/index.ts +13 -0
  39. package/packages/angular-grab/src/builder/builders/dev-server/index.ts +9 -0
  40. package/packages/angular-grab/src/builder/index.ts +3 -0
  41. package/packages/angular-grab/src/cli/__tests__/cli.test.ts +239 -0
  42. package/packages/angular-grab/src/cli/commands/init.ts +106 -0
  43. package/packages/angular-grab/src/cli/index.ts +15 -0
  44. package/packages/angular-grab/src/cli/utils/detect-project.ts +78 -0
  45. package/packages/angular-grab/src/cli/utils/modify-angular-json.ts +42 -0
  46. package/packages/angular-grab/src/cli/utils/modify-app-config.ts +42 -0
  47. package/packages/angular-grab/src/core/__tests__/generate-snippet.test.ts +149 -0
  48. package/packages/angular-grab/src/core/__tests__/plugin-registry.test.ts +286 -0
  49. package/packages/angular-grab/src/core/__tests__/store.test.ts +118 -0
  50. package/packages/angular-grab/src/core/__tests__/utils.test.ts +85 -0
  51. package/packages/angular-grab/src/core/clipboard/copy.ts +104 -0
  52. package/packages/angular-grab/src/core/clipboard/generate-snippet.ts +38 -0
  53. package/packages/angular-grab/src/core/constants.ts +10 -0
  54. package/packages/angular-grab/src/core/grab.ts +596 -0
  55. package/packages/angular-grab/src/core/index.global.ts +13 -0
  56. package/packages/angular-grab/src/core/index.ts +19 -0
  57. package/packages/angular-grab/src/core/keyboard/keyboard-handler.ts +163 -0
  58. package/packages/angular-grab/src/core/overlay/crosshair.ts +107 -0
  59. package/packages/angular-grab/src/core/overlay/freeze-overlay.ts +239 -0
  60. package/packages/angular-grab/src/core/overlay/overlay-renderer.ts +180 -0
  61. package/packages/angular-grab/src/core/overlay/select-feedback.ts +108 -0
  62. package/packages/angular-grab/src/core/overlay/toast.ts +175 -0
  63. package/packages/angular-grab/src/core/picker/element-picker.ts +114 -0
  64. package/packages/angular-grab/src/core/plugins/plugin-registry.ts +83 -0
  65. package/packages/angular-grab/src/core/store.ts +52 -0
  66. package/packages/angular-grab/src/core/toolbar/actions-menu.ts +178 -0
  67. package/packages/angular-grab/src/core/toolbar/comment-popover.ts +235 -0
  68. package/packages/angular-grab/src/core/toolbar/copy-actions.ts +98 -0
  69. package/packages/angular-grab/src/core/toolbar/history-popover.ts +245 -0
  70. package/packages/angular-grab/src/core/toolbar/theme-manager.ts +188 -0
  71. package/packages/angular-grab/src/core/toolbar/toolbar-icons.ts +29 -0
  72. package/packages/angular-grab/src/core/toolbar/toolbar-renderer.ts +239 -0
  73. package/packages/angular-grab/src/core/types.ts +139 -0
  74. package/packages/angular-grab/src/core/utils.ts +16 -0
  75. package/packages/angular-grab/src/esbuild-plugin/__tests__/transform.test.ts +174 -0
  76. package/packages/angular-grab/src/esbuild-plugin/index.ts +3 -0
  77. package/packages/angular-grab/src/esbuild-plugin/plugin.ts +29 -0
  78. package/packages/angular-grab/src/esbuild-plugin/scan.ts +105 -0
  79. package/packages/angular-grab/src/esbuild-plugin/transform.ts +152 -0
  80. package/packages/angular-grab/src/vite-plugin/__tests__/plugin.test.ts +84 -0
  81. package/packages/angular-grab/src/vite-plugin/index.ts +19 -0
  82. package/packages/angular-grab/src/webpack-plugin/__tests__/plugin.test.ts +72 -0
  83. package/packages/angular-grab/src/webpack-plugin/index.ts +2 -0
  84. package/packages/angular-grab/src/webpack-plugin/loader.ts +15 -0
  85. package/packages/angular-grab/src/webpack-plugin/plugin.ts +20 -0
  86. package/packages/angular-grab/tsconfig.json +15 -0
  87. package/packages/angular-grab/tsup.config.ts +119 -0
  88. package/pnpm-workspace.yaml +3 -0
  89. package/turbo.json +21 -0
  90. package/dist/angular/index.d.ts +0 -151
  91. package/dist/angular/index.js +0 -2811
  92. package/dist/angular/index.js.map +0 -1
  93. package/dist/builder/builders/application/index.js +0 -143
  94. package/dist/builder/builders/application/index.js.map +0 -1
  95. package/dist/builder/builders/dev-server/index.js +0 -139
  96. package/dist/builder/builders/dev-server/index.js.map +0 -1
  97. package/dist/builder/index.js +0 -2
  98. package/dist/builder/index.js.map +0 -1
  99. package/dist/builder/package.json +0 -1
  100. package/dist/cli/index.js +0 -223
  101. package/dist/cli/index.js.map +0 -1
  102. package/dist/core/index.cjs +0 -2589
  103. package/dist/core/index.cjs.map +0 -1
  104. package/dist/core/index.d.cts +0 -139
  105. package/dist/core/index.d.ts +0 -139
  106. package/dist/core/index.global.js +0 -542
  107. package/dist/core/index.js +0 -2560
  108. package/dist/core/index.js.map +0 -1
  109. package/dist/esbuild-plugin/index.cjs +0 -239
  110. package/dist/esbuild-plugin/index.cjs.map +0 -1
  111. package/dist/esbuild-plugin/index.d.cts +0 -26
  112. package/dist/esbuild-plugin/index.d.ts +0 -26
  113. package/dist/esbuild-plugin/index.js +0 -200
  114. package/dist/esbuild-plugin/index.js.map +0 -1
  115. package/dist/vite-plugin/index.d.ts +0 -7
  116. package/dist/vite-plugin/index.js +0 -128
  117. package/dist/vite-plugin/index.js.map +0 -1
  118. package/dist/webpack-plugin/index.cjs +0 -54
  119. package/dist/webpack-plugin/index.cjs.map +0 -1
  120. package/dist/webpack-plugin/index.d.cts +0 -5
  121. package/dist/webpack-plugin/index.d.ts +0 -5
  122. package/dist/webpack-plugin/index.js +0 -23
  123. package/dist/webpack-plugin/index.js.map +0 -1
  124. package/dist/webpack-plugin/loader.cjs +0 -155
  125. package/dist/webpack-plugin/loader.cjs.map +0 -1
  126. package/dist/webpack-plugin/loader.d.cts +0 -3
  127. package/dist/webpack-plugin/loader.d.ts +0 -3
  128. package/dist/webpack-plugin/loader.js +0 -122
  129. package/dist/webpack-plugin/loader.js.map +0 -1
  130. /package/{builders.json → packages/angular-grab/builders.json} +0 -0
  131. /package/{dist → packages/angular-grab/src}/builder/builders/application/schema.json +0 -0
  132. /package/{dist → packages/angular-grab/src}/builder/builders/dev-server/schema.json +0 -0
@@ -1,2560 +0,0 @@
1
- // src/core/store.ts
2
- function createStore(initialOptions) {
3
- const listeners = /* @__PURE__ */ new Set();
4
- const raw = {
5
- active: false,
6
- frozen: false,
7
- hoveredElement: null,
8
- options: initialOptions,
9
- toolbar: {
10
- visible: initialOptions.showToolbar,
11
- themeMode: initialOptions.themeMode,
12
- history: [],
13
- pendingAction: null
14
- }
15
- };
16
- const state = new Proxy(raw, {
17
- set(target, prop, value) {
18
- const key = prop;
19
- if (target[key] === value) return true;
20
- Reflect.set(target, key, value);
21
- listeners.forEach((fn) => fn(state, key));
22
- return true;
23
- }
24
- });
25
- return {
26
- state,
27
- subscribe(listener) {
28
- listeners.add(listener);
29
- return () => listeners.delete(listener);
30
- }
31
- };
32
- }
33
-
34
- // src/core/constants.ts
35
- var Z_INDEX_FREEZE = 2147483644;
36
- var Z_INDEX_CROSSHAIR = 2147483645;
37
- var Z_INDEX_OVERLAY = 2147483646;
38
- var Z_INDEX_LABEL = 2147483647;
39
- var Z_INDEX_TOOLBAR = 2147483646;
40
- var Z_INDEX_TOAST = 2147483647;
41
- var Z_INDEX_POPOVER = 2147483647;
42
- var TOOLBAR_TOAST_OFFSET = "72px";
43
- var TOOLBAR_POPOVER_OFFSET = "68px";
44
-
45
- // src/core/overlay/overlay-renderer.ts
46
- var OVERLAY_ID = "__ag-overlay__";
47
- var LABEL_ID = "__ag-label__";
48
- var STYLE_ID = "__ag-styles__";
49
- function createOverlayRenderer() {
50
- let overlay = null;
51
- let label = null;
52
- let rafId = null;
53
- let currentElement = null;
54
- let currentComponentName = null;
55
- let currentSourcePath = null;
56
- let currentCssClasses = [];
57
- function injectStyles2() {
58
- if (document.getElementById(STYLE_ID)) return;
59
- const style = document.createElement("style");
60
- style.id = STYLE_ID;
61
- style.textContent = `
62
- #${OVERLAY_ID} {
63
- position: fixed;
64
- pointer-events: none;
65
- z-index: ${Z_INDEX_OVERLAY};
66
- border: 2px solid var(--ag-overlay-border, #3b82f6);
67
- background: var(--ag-overlay-bg, rgba(59, 130, 246, 0.1));
68
- transition: top 0.05s ease, left 0.05s ease, width 0.05s ease, height 0.05s ease;
69
- box-sizing: border-box;
70
- }
71
- #${LABEL_ID} {
72
- position: fixed;
73
- pointer-events: none;
74
- z-index: ${Z_INDEX_LABEL};
75
- background: var(--ag-label-bg, #3b82f6);
76
- color: var(--ag-label-text, #fff);
77
- font: 11px/1.4 monospace;
78
- padding: 2px 6px;
79
- border-radius: 3px;
80
- white-space: nowrap;
81
- box-sizing: border-box;
82
- max-width: 100vw;
83
- overflow: hidden;
84
- text-overflow: ellipsis;
85
- }
86
- `;
87
- document.head.appendChild(style);
88
- }
89
- function ensureElements() {
90
- if (!overlay) {
91
- injectStyles2();
92
- overlay = document.createElement("div");
93
- overlay.id = OVERLAY_ID;
94
- document.body.appendChild(overlay);
95
- }
96
- if (!label) {
97
- label = document.createElement("div");
98
- label.id = LABEL_ID;
99
- document.body.appendChild(label);
100
- }
101
- }
102
- function positionOverlay() {
103
- if (!currentElement || !overlay || !label) return;
104
- const rect = currentElement.getBoundingClientRect();
105
- if (rect.width === 0 && rect.height === 0 && !currentElement.isConnected) {
106
- overlay.style.display = "none";
107
- label.style.display = "none";
108
- return;
109
- }
110
- overlay.style.top = `${rect.top}px`;
111
- overlay.style.left = `${rect.left}px`;
112
- overlay.style.width = `${rect.width}px`;
113
- overlay.style.height = `${rect.height}px`;
114
- overlay.style.display = "block";
115
- const tag = currentElement.tagName.toLowerCase();
116
- let labelText = `<${tag}>`;
117
- if (currentCssClasses.length > 0) {
118
- labelText += ` .${currentCssClasses.join(".")}`;
119
- }
120
- if (currentComponentName) {
121
- labelText += ` in ${currentComponentName}`;
122
- }
123
- if (currentSourcePath) {
124
- labelText += ` \u2014 ${currentSourcePath}`;
125
- }
126
- label.textContent = labelText;
127
- const labelHeight = 20;
128
- const gap = 4;
129
- let labelTop = rect.top - labelHeight - gap;
130
- if (labelTop < 0) {
131
- labelTop = rect.bottom + gap;
132
- }
133
- let labelLeft = rect.left;
134
- label.style.top = `${labelTop}px`;
135
- label.style.left = `${labelLeft}px`;
136
- label.style.display = "block";
137
- const labelRect = label.getBoundingClientRect();
138
- const viewportWidth = document.documentElement.clientWidth;
139
- if (labelRect.right > viewportWidth) {
140
- labelLeft = Math.max(0, viewportWidth - labelRect.width);
141
- label.style.left = `${labelLeft}px`;
142
- }
143
- if (labelLeft < 0) {
144
- label.style.left = "0px";
145
- }
146
- }
147
- function trackPosition() {
148
- positionOverlay();
149
- rafId = requestAnimationFrame(trackPosition);
150
- }
151
- function stopTracking() {
152
- if (rafId !== null) {
153
- cancelAnimationFrame(rafId);
154
- rafId = null;
155
- }
156
- }
157
- return {
158
- show(element, componentName, sourcePath, cssClasses) {
159
- ensureElements();
160
- currentElement = element;
161
- currentComponentName = componentName;
162
- currentSourcePath = sourcePath ?? null;
163
- currentCssClasses = cssClasses ?? [];
164
- stopTracking();
165
- trackPosition();
166
- },
167
- hide() {
168
- stopTracking();
169
- currentElement = null;
170
- currentComponentName = null;
171
- currentSourcePath = null;
172
- currentCssClasses = [];
173
- if (overlay) overlay.style.display = "none";
174
- if (label) label.style.display = "none";
175
- },
176
- isOverlayElement(el) {
177
- return el === overlay || el === label || el.id === OVERLAY_ID || el.id === LABEL_ID;
178
- },
179
- dispose() {
180
- stopTracking();
181
- currentElement = null;
182
- currentComponentName = null;
183
- currentSourcePath = null;
184
- currentCssClasses = [];
185
- overlay?.remove();
186
- label?.remove();
187
- document.getElementById(STYLE_ID)?.remove();
188
- overlay = null;
189
- label = null;
190
- }
191
- };
192
- }
193
-
194
- // src/core/overlay/crosshair.ts
195
- var CROSSHAIR_STYLE_ID = "__ag-crosshair-styles__";
196
- var H_LINE_ID = "__ag-crosshair-h__";
197
- var V_LINE_ID = "__ag-crosshair-v__";
198
- function createCrosshair() {
199
- let hLine = null;
200
- let vLine = null;
201
- let listening = false;
202
- function injectStyles2() {
203
- if (document.getElementById(CROSSHAIR_STYLE_ID)) return;
204
- const style = document.createElement("style");
205
- style.id = CROSSHAIR_STYLE_ID;
206
- style.textContent = `
207
- .ag-crosshair-line {
208
- position: fixed;
209
- pointer-events: none;
210
- z-index: ${Z_INDEX_CROSSHAIR};
211
- background: var(--ag-accent, #3b82f6);
212
- opacity: 0.25;
213
- transition: none;
214
- }
215
- #${H_LINE_ID} {
216
- left: 0;
217
- right: 0;
218
- height: 1px;
219
- }
220
- #${V_LINE_ID} {
221
- top: 0;
222
- bottom: 0;
223
- width: 1px;
224
- }
225
- body.ag-crosshair-active {
226
- cursor: crosshair !important;
227
- }
228
- `;
229
- document.head.appendChild(style);
230
- }
231
- function ensureElements() {
232
- if (!hLine) {
233
- injectStyles2();
234
- hLine = document.createElement("div");
235
- hLine.id = H_LINE_ID;
236
- hLine.className = "ag-crosshair-line";
237
- document.body.appendChild(hLine);
238
- }
239
- if (!vLine) {
240
- vLine = document.createElement("div");
241
- vLine.id = V_LINE_ID;
242
- vLine.className = "ag-crosshair-line";
243
- document.body.appendChild(vLine);
244
- }
245
- }
246
- function handleMouseMove(e) {
247
- if (hLine) {
248
- hLine.style.top = `${e.clientY}px`;
249
- }
250
- if (vLine) {
251
- vLine.style.left = `${e.clientX}px`;
252
- }
253
- }
254
- return {
255
- activate() {
256
- if (listening) return;
257
- listening = true;
258
- ensureElements();
259
- document.body.classList.add("ag-crosshair-active");
260
- document.addEventListener("mousemove", handleMouseMove, true);
261
- },
262
- deactivate() {
263
- if (!listening) return;
264
- listening = false;
265
- document.body.classList.remove("ag-crosshair-active");
266
- document.removeEventListener("mousemove", handleMouseMove, true);
267
- if (hLine) hLine.style.top = "-10px";
268
- if (vLine) vLine.style.left = "-10px";
269
- },
270
- isCrosshairElement(el) {
271
- return el === hLine || el === vLine || el.id === H_LINE_ID || el.id === V_LINE_ID;
272
- },
273
- dispose() {
274
- this.deactivate();
275
- hLine?.remove();
276
- vLine?.remove();
277
- document.getElementById(CROSSHAIR_STYLE_ID)?.remove();
278
- hLine = null;
279
- vLine = null;
280
- }
281
- };
282
- }
283
-
284
- // src/core/utils.ts
285
- function escapeHtml(text) {
286
- const div = document.createElement("div");
287
- div.textContent = text;
288
- return div.innerHTML;
289
- }
290
- function filterAngularClasses(classList) {
291
- return Array.from(classList).filter((c) => !c.startsWith("ng-") && !c.startsWith("_ng"));
292
- }
293
- var NG_ATTR_RE = /\s_ng(host|content)-[a-z0-9-]+="[^"]*"/gi;
294
- var NG_ATTR_EMPTY_RE = /\s_ng(host|content)-[a-z0-9-]+/gi;
295
- function cleanAngularAttrs(html) {
296
- return html.replace(NG_ATTR_RE, "").replace(NG_ATTR_EMPTY_RE, "");
297
- }
298
-
299
- // src/core/overlay/toast.ts
300
- var TOAST_ID = "__ag-toast__";
301
- var TOAST_STYLE_ID = "__ag-toast-styles__";
302
- var activeTimer = null;
303
- function injectToastStyles() {
304
- if (document.getElementById(TOAST_STYLE_ID)) return;
305
- const style = document.createElement("style");
306
- style.id = TOAST_STYLE_ID;
307
- style.textContent = `
308
- #${TOAST_ID} {
309
- position: fixed;
310
- bottom: var(--ag-toast-bottom, 24px);
311
- left: 50%;
312
- transform: translateX(-50%) translateY(100%);
313
- z-index: ${Z_INDEX_TOAST};
314
- background: var(--ag-toast-bg, #0f172a);
315
- color: var(--ag-toast-text, #e2e8f0);
316
- font: 500 13px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
317
- padding: 12px 18px;
318
- border-radius: 10px;
319
- box-shadow: 0 8px 24px var(--ag-toast-shadow, rgba(0, 0, 0, 0.4));
320
- pointer-events: none;
321
- opacity: 0;
322
- transition: transform 0.25s ease, opacity 0.25s ease;
323
- letter-spacing: 0.01em;
324
- max-width: 480px;
325
- min-width: 260px;
326
- }
327
- #${TOAST_ID}.ag-toast-visible {
328
- transform: translateX(-50%) translateY(0);
329
- opacity: 1;
330
- }
331
- #${TOAST_ID} .ag-toast-header {
332
- display: flex;
333
- align-items: center;
334
- gap: 8px;
335
- margin-bottom: 0;
336
- }
337
- #${TOAST_ID} .ag-toast-icon {
338
- flex-shrink: 0;
339
- width: 16px;
340
- height: 16px;
341
- }
342
- #${TOAST_ID} .ag-toast-title {
343
- font-weight: 600;
344
- color: var(--ag-toast-title, #fff);
345
- }
346
- #${TOAST_ID} .ag-toast-details {
347
- margin-top: 8px;
348
- display: flex;
349
- flex-direction: column;
350
- gap: 4px;
351
- font-size: 12px;
352
- font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
353
- }
354
- #${TOAST_ID} .ag-toast-row {
355
- display: flex;
356
- gap: 8px;
357
- align-items: baseline;
358
- }
359
- #${TOAST_ID} .ag-toast-label {
360
- color: var(--ag-toast-label, #64748b);
361
- flex-shrink: 0;
362
- min-width: 72px;
363
- }
364
- #${TOAST_ID} .ag-toast-value {
365
- color: var(--ag-toast-text, #e2e8f0);
366
- overflow: hidden;
367
- text-overflow: ellipsis;
368
- white-space: nowrap;
369
- }
370
- #${TOAST_ID} .ag-toast-file-link {
371
- color: var(--ag-toast-text, #e2e8f0);
372
- text-decoration: none;
373
- overflow: hidden;
374
- text-overflow: ellipsis;
375
- white-space: nowrap;
376
- pointer-events: auto;
377
- cursor: pointer;
378
- }
379
- #${TOAST_ID} .ag-toast-file-link:hover {
380
- text-decoration: underline;
381
- color: var(--ag-accent, #3b82f6);
382
- }
383
- `;
384
- document.head.appendChild(style);
385
- }
386
- function getOrCreateToast() {
387
- let toast = document.getElementById(TOAST_ID);
388
- if (!toast) {
389
- injectToastStyles();
390
- toast = document.createElement("div");
391
- toast.id = TOAST_ID;
392
- document.body.appendChild(toast);
393
- }
394
- return toast;
395
- }
396
- var CHECKMARK_SVG = `<svg class="ag-toast-icon" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="8" cy="8" r="7" fill="#22c55e"/><path d="M5 8l2 2 4-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
397
- function showToast(message, detail, durationMs = 3500) {
398
- const toast = getOrCreateToast();
399
- let html = `<div class="ag-toast-header">${CHECKMARK_SVG}<span class="ag-toast-title">${escapeHtml(message)}</span></div>`;
400
- if (detail) {
401
- html += '<div class="ag-toast-details">';
402
- if (detail.componentName) {
403
- html += `<div class="ag-toast-row"><span class="ag-toast-label">Component</span><span class="ag-toast-value">${escapeHtml(detail.componentName)}</span></div>`;
404
- }
405
- if (detail.filePath) {
406
- let loc = detail.filePath;
407
- if (detail.line != null) loc += `:${detail.line}`;
408
- let vsCodeUri = `vscode://file/${encodeURI(detail.filePath)}`;
409
- if (detail.line != null) vsCodeUri += `:${detail.line}`;
410
- if (detail.line != null && detail.column != null) vsCodeUri += `:${detail.column}`;
411
- html += `<div class="ag-toast-row"><span class="ag-toast-label">File</span>`;
412
- html += `<a class="ag-toast-file-link" href="${escapeHtml(vsCodeUri)}" title="Open in VS Code">${escapeHtml(loc)}</a>`;
413
- html += `</div>`;
414
- }
415
- if (detail.cssClasses && detail.cssClasses.length > 0) {
416
- const classes = detail.cssClasses.map((c) => `.${escapeHtml(c)}`).join(" ");
417
- html += `<div class="ag-toast-row"><span class="ag-toast-label">Classes</span><span class="ag-toast-value">${classes}</span></div>`;
418
- }
419
- html += "</div>";
420
- }
421
- toast.innerHTML = html;
422
- if (activeTimer) {
423
- clearTimeout(activeTimer);
424
- activeTimer = null;
425
- }
426
- toast.classList.remove("ag-toast-visible");
427
- void toast.offsetHeight;
428
- toast.classList.add("ag-toast-visible");
429
- activeTimer = setTimeout(() => {
430
- toast.classList.remove("ag-toast-visible");
431
- activeTimer = null;
432
- }, durationMs);
433
- }
434
- function disposeToast() {
435
- if (activeTimer) {
436
- clearTimeout(activeTimer);
437
- activeTimer = null;
438
- }
439
- document.getElementById(TOAST_ID)?.remove();
440
- document.getElementById(TOAST_STYLE_ID)?.remove();
441
- }
442
-
443
- // src/core/picker/element-picker.ts
444
- function createElementPicker(deps) {
445
- let hoveredElement = null;
446
- let listening = false;
447
- function resolveComponentName(el) {
448
- const resolver = deps.getComponentResolver();
449
- if (!resolver) return null;
450
- const result = resolver(el);
451
- return result?.name ?? null;
452
- }
453
- function resolveSourcePath(el) {
454
- const resolver = deps.getSourceResolver();
455
- if (!resolver) return null;
456
- const result = resolver(el);
457
- if (!result?.filePath) return null;
458
- let path = result.filePath;
459
- if (result.line != null) {
460
- path += `:${result.line}`;
461
- }
462
- return path;
463
- }
464
- function elementAtPoint(x, y) {
465
- const freezeEl = deps.getFreezeElement?.();
466
- if (freezeEl) freezeEl.style.pointerEvents = "none";
467
- const target = document.elementFromPoint(x, y);
468
- if (freezeEl) freezeEl.style.pointerEvents = "auto";
469
- return target;
470
- }
471
- function handleMouseMove(e) {
472
- const target = elementAtPoint(e.clientX, e.clientY);
473
- if (!target || deps.overlay.isOverlayElement(target)) return;
474
- if (deps.crosshair.isCrosshairElement(target)) return;
475
- if (deps.isToolbarElement?.(target)) return;
476
- if (target === hoveredElement) return;
477
- hoveredElement = target;
478
- const componentName = resolveComponentName(target);
479
- const sourcePath = resolveSourcePath(target);
480
- const cssClasses = filterAngularClasses(target.classList);
481
- deps.overlay.show(target, componentName, sourcePath, cssClasses);
482
- deps.onHover(target);
483
- }
484
- function handleClick(e) {
485
- const target = elementAtPoint(e.clientX, e.clientY);
486
- if (target && (deps.isToolbarElement?.(target) || deps.crosshair.isCrosshairElement(target))) return;
487
- e.preventDefault();
488
- e.stopPropagation();
489
- if (hoveredElement) {
490
- deps.onSelect(hoveredElement);
491
- }
492
- }
493
- return {
494
- activate() {
495
- if (listening) return;
496
- listening = true;
497
- deps.crosshair.activate();
498
- document.addEventListener("mousemove", handleMouseMove, true);
499
- document.addEventListener("click", handleClick, true);
500
- },
501
- deactivate() {
502
- if (!listening) return;
503
- listening = false;
504
- hoveredElement = null;
505
- deps.crosshair.deactivate();
506
- document.removeEventListener("mousemove", handleMouseMove, true);
507
- document.removeEventListener("click", handleClick, true);
508
- deps.overlay.hide();
509
- deps.onHover(null);
510
- },
511
- getHoveredElement() {
512
- return hoveredElement;
513
- },
514
- dispose() {
515
- this.deactivate();
516
- }
517
- };
518
- }
519
-
520
- // src/core/keyboard/keyboard-handler.ts
521
- function isMac() {
522
- if (typeof navigator === "undefined") return false;
523
- const uaData = navigator.userAgentData;
524
- if (uaData?.platform) return /mac/i.test(uaData.platform);
525
- return /Mac|iPhone|iPad|iPod/i.test(navigator.userAgent);
526
- }
527
- function parseKeyCombo(combo) {
528
- const parts = combo.split("+").map((s) => s.trim());
529
- const result = {
530
- key: "",
531
- meta: false,
532
- ctrl: false,
533
- shift: false,
534
- alt: false
535
- };
536
- for (const part of parts) {
537
- const lower = part.toLowerCase();
538
- if (lower === "meta" || lower === "cmd" || lower === "command") {
539
- result.meta = true;
540
- } else if (lower === "ctrl" || lower === "control") {
541
- result.ctrl = true;
542
- } else if (lower === "shift") {
543
- result.shift = true;
544
- } else if (lower === "alt" || lower === "option") {
545
- result.alt = true;
546
- } else {
547
- result.key = lower;
548
- }
549
- }
550
- return result;
551
- }
552
- function matchesCombo(e, parsed) {
553
- if (parsed.meta && !e.metaKey) return false;
554
- if (parsed.ctrl && !e.ctrlKey) return false;
555
- if (parsed.shift && !e.shiftKey) return false;
556
- if (parsed.alt && !e.altKey) return false;
557
- return e.key.toLowerCase() === parsed.key;
558
- }
559
- function isInputElement(el) {
560
- if (!el || !(el instanceof HTMLElement)) return false;
561
- const tag = el.tagName;
562
- return tag === "INPUT" || tag === "TEXTAREA" || el.isContentEditable;
563
- }
564
- function createKeyboardHandler(deps) {
565
- let holdTimer = null;
566
- let holdActivated = false;
567
- let listening = false;
568
- function handleKeyDown(e) {
569
- if (!deps.getEnableInInputs() && isInputElement(e.target)) return;
570
- const parsed = parseKeyCombo(deps.getActivationKey());
571
- if (!matchesCombo(e, parsed)) return;
572
- const mode = deps.getActivationMode();
573
- const holdDuration = deps.getKeyHoldDuration();
574
- if (mode === "hold") {
575
- e.preventDefault();
576
- if (holdActivated) return;
577
- if (holdDuration > 0) {
578
- if (holdTimer) return;
579
- holdTimer = setTimeout(() => {
580
- holdActivated = true;
581
- deps.onActivate();
582
- }, holdDuration);
583
- } else {
584
- holdActivated = true;
585
- deps.onActivate();
586
- }
587
- } else {
588
- e.preventDefault();
589
- }
590
- }
591
- function handleKeyUp(e) {
592
- const parsed = parseKeyCombo(deps.getActivationKey());
593
- if (e.key.toLowerCase() !== parsed.key) return;
594
- const mode = deps.getActivationMode();
595
- if (mode === "hold") {
596
- if (holdTimer) {
597
- clearTimeout(holdTimer);
598
- holdTimer = null;
599
- }
600
- if (holdActivated) {
601
- holdActivated = false;
602
- deps.onDeactivate();
603
- }
604
- } else {
605
- if (deps.isActive()) {
606
- deps.onDeactivate();
607
- } else {
608
- deps.onActivate();
609
- }
610
- }
611
- }
612
- return {
613
- start() {
614
- if (listening) return;
615
- listening = true;
616
- document.addEventListener("keydown", handleKeyDown, true);
617
- document.addEventListener("keyup", handleKeyUp, true);
618
- },
619
- stop() {
620
- if (!listening) return;
621
- listening = false;
622
- if (holdTimer) {
623
- clearTimeout(holdTimer);
624
- holdTimer = null;
625
- }
626
- holdActivated = false;
627
- document.removeEventListener("keydown", handleKeyDown, true);
628
- document.removeEventListener("keyup", handleKeyUp, true);
629
- },
630
- dispose() {
631
- this.stop();
632
- }
633
- };
634
- }
635
-
636
- // src/core/clipboard/generate-snippet.ts
637
- function truncateHtml(html, maxLines) {
638
- const lines = html.split("\n");
639
- if (lines.length <= maxLines) return html;
640
- return lines.slice(0, maxLines).join("\n") + "\n ...";
641
- }
642
- function formatLocation(name, filePath, line, column) {
643
- let locationLine = "";
644
- if (name) locationLine += `in ${name}`;
645
- if (filePath) {
646
- const loc = filePath + (line != null ? `:${line}` : "") + (line != null && column != null ? `:${column}` : "");
647
- locationLine += locationLine ? ` at ${loc}` : `at ${loc}`;
648
- }
649
- return locationLine;
650
- }
651
- function generateSnippet(context, maxContextLines) {
652
- const cleaned = cleanAngularAttrs(context.html);
653
- const truncated = truncateHtml(cleaned, maxContextLines);
654
- const parts = [truncated];
655
- if (context.componentStack.length > 0) {
656
- for (const entry of context.componentStack) {
657
- parts.push(formatLocation(entry.name, entry.filePath, entry.line, entry.column));
658
- }
659
- } else if (context.componentName || context.filePath) {
660
- parts.push(formatLocation(context.componentName, context.filePath, context.line, context.column));
661
- }
662
- return parts.join("\n");
663
- }
664
-
665
- // src/core/clipboard/copy.ts
666
- function buildSelector(el) {
667
- const tag = el.tagName.toLowerCase();
668
- const id = el.id ? `#${el.id}` : "";
669
- const classes = filterAngularClasses(el.classList).map((c) => `.${c}`).join("");
670
- return `${tag}${id}${classes}`;
671
- }
672
- function getCssClasses(el) {
673
- return filterAngularClasses(el.classList);
674
- }
675
- function buildElementContext(element, componentResolver, sourceResolver) {
676
- const compResult = componentResolver?.(element);
677
- const srcResult = sourceResolver?.(element);
678
- const componentStack = [];
679
- if (compResult?.stack) {
680
- for (const entry of compResult.stack) {
681
- let filePath = null;
682
- let line = null;
683
- let column = null;
684
- if (entry.hostElement && sourceResolver) {
685
- const src = sourceResolver(entry.hostElement);
686
- if (src) {
687
- filePath = src.filePath;
688
- line = src.line;
689
- column = src.column;
690
- }
691
- }
692
- componentStack.push({ name: entry.name, filePath, line, column });
693
- }
694
- }
695
- return {
696
- element,
697
- html: element.outerHTML,
698
- componentName: compResult?.name ?? null,
699
- filePath: srcResult?.filePath ?? null,
700
- line: srcResult?.line ?? null,
701
- column: srcResult?.column ?? null,
702
- componentStack,
703
- selector: buildSelector(element),
704
- cssClasses: getCssClasses(element)
705
- };
706
- }
707
- async function copyElement(element, deps) {
708
- const context = buildElementContext(
709
- element,
710
- deps.getComponentResolver(),
711
- deps.getSourceResolver()
712
- );
713
- deps.pluginRegistry.callHook("onElementSelect", context);
714
- deps.pluginRegistry.callHook("onBeforeCopy", context);
715
- let snippet = generateSnippet(context, deps.getMaxContextLines());
716
- snippet = deps.pluginRegistry.callTransformHook(snippet, context);
717
- try {
718
- await navigator.clipboard.writeText(snippet);
719
- const detail = {
720
- componentName: context.componentName,
721
- filePath: context.filePath,
722
- line: context.line,
723
- column: context.column,
724
- cssClasses: context.cssClasses
725
- };
726
- showToast("Copied to clipboard", detail);
727
- deps.pluginRegistry.callHook("onCopySuccess", snippet, context, void 0);
728
- return { context, snippet };
729
- } catch (err) {
730
- const error = err instanceof Error ? err : new Error(String(err));
731
- deps.pluginRegistry.callHook("onCopyError", error);
732
- return null;
733
- }
734
- }
735
-
736
- // src/core/plugins/plugin-registry.ts
737
- function createPluginRegistry() {
738
- const plugins = /* @__PURE__ */ new Map();
739
- const cleanups = /* @__PURE__ */ new Map();
740
- return {
741
- register(plugin, api) {
742
- if (plugins.has(plugin.name)) {
743
- this.unregister(plugin.name);
744
- }
745
- plugins.set(plugin.name, plugin);
746
- if (plugin.setup) {
747
- const cleanup = plugin.setup(api);
748
- if (cleanup) {
749
- cleanups.set(plugin.name, cleanup);
750
- }
751
- }
752
- },
753
- unregister(name) {
754
- const cleanup = cleanups.get(name);
755
- if (cleanup) {
756
- cleanup();
757
- cleanups.delete(name);
758
- }
759
- plugins.delete(name);
760
- },
761
- callHook(hookName, ...args) {
762
- for (const plugin of plugins.values()) {
763
- const hook = plugin.hooks?.[hookName];
764
- if (hook) {
765
- try {
766
- hook(...args);
767
- } catch (err) {
768
- console.warn(`[angular-grab] Plugin "${plugin.name}" hook "${hookName}" threw:`, err);
769
- }
770
- }
771
- }
772
- },
773
- callTransformHook(text, context) {
774
- let result = text;
775
- for (const plugin of plugins.values()) {
776
- const transform = plugin.hooks?.transformCopyContent;
777
- if (transform) {
778
- try {
779
- result = transform(result, context);
780
- } catch (err) {
781
- console.warn(`[angular-grab] Plugin "${plugin.name}" transformCopyContent threw:`, err);
782
- }
783
- }
784
- }
785
- return result;
786
- },
787
- getPlugins() {
788
- return Array.from(plugins.values());
789
- },
790
- dispose() {
791
- for (const [name] of plugins) {
792
- const cleanup = cleanups.get(name);
793
- if (cleanup) {
794
- try {
795
- cleanup();
796
- } catch {
797
- }
798
- }
799
- }
800
- cleanups.clear();
801
- plugins.clear();
802
- }
803
- };
804
- }
805
-
806
- // src/core/toolbar/theme-manager.ts
807
- var STYLE_ID2 = "__ag-theme-vars__";
808
- var OVERRIDE_STYLE_ID = "__ag-theme-overrides__";
809
- var DARK_VARS = `
810
- :root {
811
- --ag-bg: #0f172a;
812
- --ag-text: #e2e8f0;
813
- --ag-text-muted: #64748b;
814
- --ag-accent: #3b82f6;
815
- --ag-accent-hover: #2563eb;
816
- --ag-surface: #1e293b;
817
- --ag-border: #334155;
818
- --ag-overlay-border: #3b82f6;
819
- --ag-overlay-bg: rgba(59, 130, 246, 0.1);
820
- --ag-label-bg: #3b82f6;
821
- --ag-label-text: #fff;
822
- --ag-toast-bg: #0f172a;
823
- --ag-toast-text: #e2e8f0;
824
- --ag-toast-title: #fff;
825
- --ag-toast-label: #64748b;
826
- --ag-toast-shadow: rgba(0, 0, 0, 0.4);
827
- --ag-toolbar-bg: #0f172a;
828
- --ag-toolbar-text: #94a3b8;
829
- --ag-toolbar-hover: #1e293b;
830
- --ag-toolbar-active: #3b82f6;
831
- --ag-toolbar-border: #1e293b;
832
- --ag-toolbar-shadow: rgba(0, 0, 0, 0.5);
833
- --ag-popover-bg: #0f172a;
834
- --ag-popover-text: #e2e8f0;
835
- --ag-popover-border: #1e293b;
836
- --ag-popover-hover: #1e293b;
837
- --ag-popover-shadow: rgba(0, 0, 0, 0.5);
838
- }
839
- `;
840
- var LIGHT_VARS = `
841
- :root {
842
- --ag-bg: #ffffff;
843
- --ag-text: #334155;
844
- --ag-text-muted: #94a3b8;
845
- --ag-accent: #2563eb;
846
- --ag-accent-hover: #1d4ed8;
847
- --ag-surface: #f1f5f9;
848
- --ag-border: #e2e8f0;
849
- --ag-overlay-border: #2563eb;
850
- --ag-overlay-bg: rgba(37, 99, 235, 0.08);
851
- --ag-label-bg: #2563eb;
852
- --ag-label-text: #fff;
853
- --ag-toast-bg: #ffffff;
854
- --ag-toast-text: #334155;
855
- --ag-toast-title: #0f172a;
856
- --ag-toast-label: #94a3b8;
857
- --ag-toast-shadow: rgba(0, 0, 0, 0.12);
858
- --ag-toolbar-bg: #ffffff;
859
- --ag-toolbar-text: #64748b;
860
- --ag-toolbar-hover: #f1f5f9;
861
- --ag-toolbar-active: #2563eb;
862
- --ag-toolbar-border: #e2e8f0;
863
- --ag-toolbar-shadow: rgba(0, 0, 0, 0.12);
864
- --ag-popover-bg: #ffffff;
865
- --ag-popover-text: #334155;
866
- --ag-popover-border: #e2e8f0;
867
- --ag-popover-hover: #f1f5f9;
868
- --ag-popover-shadow: rgba(0, 0, 0, 0.12);
869
- }
870
- `;
871
- var THEME_TO_VAR = {
872
- overlayBorderColor: "--ag-overlay-border",
873
- overlayBgColor: "--ag-overlay-bg",
874
- labelBgColor: "--ag-label-bg",
875
- labelTextColor: "--ag-label-text",
876
- toastBgColor: "--ag-toast-bg",
877
- toastTextColor: "--ag-toast-text",
878
- toolbarBgColor: "--ag-toolbar-bg",
879
- toolbarTextColor: "--ag-toolbar-text",
880
- toolbarAccentColor: "--ag-toolbar-active",
881
- popoverBgColor: "--ag-popover-bg",
882
- popoverTextColor: "--ag-popover-text",
883
- popoverBorderColor: "--ag-popover-border"
884
- };
885
- function createThemeManager() {
886
- let styleEl = null;
887
- let overrideEl = null;
888
- let currentMode = "dark";
889
- let mediaQuery = null;
890
- let mediaHandler = null;
891
- function getOrCreateStyle() {
892
- if (styleEl) return styleEl;
893
- const existing = document.getElementById(STYLE_ID2);
894
- if (existing) {
895
- styleEl = existing;
896
- return styleEl;
897
- }
898
- styleEl = document.createElement("style");
899
- styleEl.id = STYLE_ID2;
900
- document.head.appendChild(styleEl);
901
- return styleEl;
902
- }
903
- function getOrCreateOverrideStyle() {
904
- if (overrideEl) return overrideEl;
905
- overrideEl = document.createElement("style");
906
- overrideEl.id = OVERRIDE_STYLE_ID;
907
- document.head.appendChild(overrideEl);
908
- return overrideEl;
909
- }
910
- function resolveMode(mode) {
911
- if (mode !== "system") return mode;
912
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
913
- }
914
- function applyResolved(resolved) {
915
- const el = getOrCreateStyle();
916
- el.textContent = resolved === "dark" ? DARK_VARS : LIGHT_VARS;
917
- }
918
- function detachMediaListener() {
919
- if (mediaQuery && mediaHandler) {
920
- mediaQuery.removeEventListener("change", mediaHandler);
921
- }
922
- mediaQuery = null;
923
- mediaHandler = null;
924
- }
925
- return {
926
- apply(mode) {
927
- currentMode = mode;
928
- detachMediaListener();
929
- applyResolved(resolveMode(mode));
930
- if (mode === "system") {
931
- mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
932
- mediaHandler = () => applyResolved(resolveMode("system"));
933
- mediaQuery.addEventListener("change", mediaHandler);
934
- }
935
- },
936
- applyOverrides(theme) {
937
- const vars = [];
938
- for (const [key, varName] of Object.entries(THEME_TO_VAR)) {
939
- const value = theme[key];
940
- if (value) {
941
- vars.push(` ${varName}: ${value};`);
942
- }
943
- }
944
- if (vars.length === 0) {
945
- this.clearOverrides();
946
- return;
947
- }
948
- const el = getOrCreateOverrideStyle();
949
- el.textContent = ` :root {
950
- ${vars.join("\n")}
951
- }`;
952
- },
953
- clearOverrides() {
954
- overrideEl?.remove();
955
- document.getElementById(OVERRIDE_STYLE_ID)?.remove();
956
- overrideEl = null;
957
- },
958
- dispose() {
959
- detachMediaListener();
960
- styleEl?.remove();
961
- document.getElementById(STYLE_ID2)?.remove();
962
- styleEl = null;
963
- this.clearOverrides();
964
- }
965
- };
966
- }
967
-
968
- // src/core/toolbar/toolbar-icons.ts
969
- var ICON_GRAB = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M5.5 7V3.5a1 1 0 0 1 2 0V7"/><path d="M7.5 6.5V2.5a1 1 0 0 1 2 0v4"/><path d="M9.5 7V3.5a1 1 0 0 1 2 0V8"/><path d="M5.5 7V5.5a1 1 0 0 0-2 0V9a4 4 0 0 0 4 4h1.5a4 4 0 0 0 4-4V6a1 1 0 0 0-2 0"/></svg>`;
970
- var ICON_HISTORY = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><circle cx="8" cy="8" r="6"/><path d="M8 4.5V8l2.5 1.5"/></svg>`;
971
- var ICON_ELLIPSIS = `<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="8" r="1.25"/><circle cx="8" cy="8" r="1.25"/><circle cx="12" cy="8" r="1.25"/></svg>`;
972
- var ICON_SUN = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" xmlns="http://www.w3.org/2000/svg"><circle cx="8" cy="8" r="3"/><path d="M8 1.5v1M8 13.5v1M1.5 8h1M13.5 8h1M3.4 3.4l.7.7M11.9 11.9l.7.7M3.4 12.6l.7-.7M11.9 4.1l.7-.7"/></svg>`;
973
- var ICON_MOON = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M13.5 8.5a5.5 5.5 0 0 1-7-7 5.5 5.5 0 1 0 7 7z"/></svg>`;
974
- var ICON_SYSTEM = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="12" height="9" rx="1"/><path d="M5.5 14h5"/><path d="M8 11v3"/></svg>`;
975
- var ICON_POWER = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M8 2v5"/><path d="M4.5 4a5.5 5.5 0 1 0 7 0"/></svg>`;
976
- var ICON_DISMISS = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" xmlns="http://www.w3.org/2000/svg"><path d="M4 4l8 8M12 4l-8 8"/></svg>`;
977
- var ICON_COPY = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><rect x="5.5" y="5.5" width="7" height="8" rx="1"/><path d="M3.5 10.5v-7a1 1 0 0 1 1-1h5"/></svg>`;
978
- var ICON_STYLES = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M13 1.5l-1.3 4.3a1 1 0 0 1-.7.7L6.7 7.8a1 1 0 0 0-.7.7L4.7 12.8a1 1 0 0 1-.7.7L1.5 14"/><path d="M7.5 5.5l3 3"/></svg>`;
979
- var ICON_CODE = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M5 4.5L1.5 8 5 11.5"/><path d="M11 4.5l3.5 3.5-3.5 3.5"/><path d="M9.5 2.5l-3 11"/></svg>`;
980
- var ICON_COMMENT = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H5l-3 3V3z"/></svg>`;
981
- var ICON_TRASH = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M2.5 4.5h11"/><path d="M5.5 4.5V3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1.5"/><path d="M4 4.5l.5 8.5a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1l.5-8.5"/></svg>`;
982
- var ICON_FREEZE = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path d="M8 1v14"/><path d="M1 8h14"/><path d="M8 1l2 2M8 1L6 3"/><path d="M8 15l2-2M8 15l-2-2"/><path d="M1 8l2 2M1 8l2-2"/><path d="M15 8l-2 2M15 8l-2-2"/></svg>`;
983
-
984
- // src/core/toolbar/toolbar-renderer.ts
985
- var TOOLBAR_ID = "__ag-toolbar__";
986
- var STYLE_ID3 = "__ag-toolbar-styles__";
987
- function createToolbarRenderer(callbacks) {
988
- let container = null;
989
- let leftGroup = null;
990
- let buttons = {};
991
- let allElements = /* @__PURE__ */ new Set();
992
- function injectStyles2() {
993
- if (document.getElementById(STYLE_ID3)) return;
994
- const style = document.createElement("style");
995
- style.id = STYLE_ID3;
996
- style.textContent = `
997
- #${TOOLBAR_ID} {
998
- position: fixed;
999
- bottom: 20px;
1000
- left: 50%;
1001
- transform: translateX(-50%);
1002
- z-index: ${Z_INDEX_TOOLBAR};
1003
- display: flex;
1004
- align-items: center;
1005
- gap: 2px;
1006
- padding: 4px 6px;
1007
- background: var(--ag-toolbar-bg, #0f172a);
1008
- border: 1px solid var(--ag-toolbar-border, #1e293b);
1009
- border-radius: 24px;
1010
- box-shadow: 0 4px 16px var(--ag-toolbar-shadow, rgba(0, 0, 0, 0.5));
1011
- pointer-events: auto;
1012
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1013
- transition: opacity 0.2s ease, transform 0.2s ease;
1014
- }
1015
- #${TOOLBAR_ID}.ag-toolbar-hidden {
1016
- opacity: 0;
1017
- transform: translateX(-50%) translateY(20px);
1018
- pointer-events: none;
1019
- }
1020
- #${TOOLBAR_ID} button {
1021
- display: flex;
1022
- align-items: center;
1023
- justify-content: center;
1024
- width: 32px;
1025
- height: 32px;
1026
- border: none;
1027
- border-radius: 8px;
1028
- background: transparent;
1029
- color: var(--ag-toolbar-text, #94a3b8);
1030
- cursor: pointer;
1031
- padding: 0;
1032
- transition: background 0.15s ease, color 0.15s ease;
1033
- }
1034
- #${TOOLBAR_ID} button:hover {
1035
- background: var(--ag-toolbar-hover, #1e293b);
1036
- color: var(--ag-accent, #3b82f6);
1037
- }
1038
- #${TOOLBAR_ID} button.ag-btn-active {
1039
- color: var(--ag-toolbar-active, #3b82f6);
1040
- }
1041
- #${TOOLBAR_ID} button.ag-btn-disabled {
1042
- opacity: 0.4;
1043
- color: var(--ag-toolbar-text, #94a3b8);
1044
- }
1045
- #${TOOLBAR_ID} .ag-toolbar-divider {
1046
- width: 1px;
1047
- height: 20px;
1048
- background: var(--ag-toolbar-border, #1e293b);
1049
- margin: 0 4px;
1050
- flex-shrink: 0;
1051
- }
1052
- #${TOOLBAR_ID} .ag-toolbar-left {
1053
- display: flex;
1054
- align-items: center;
1055
- gap: 2px;
1056
- overflow: hidden;
1057
- max-width: 240px;
1058
- opacity: 1;
1059
- transition: max-width 0.25s ease, opacity 0.2s ease, margin 0.25s ease;
1060
- }
1061
- #${TOOLBAR_ID} .ag-toolbar-left.ag-toolbar-left-hidden {
1062
- max-width: 0;
1063
- opacity: 0;
1064
- pointer-events: none;
1065
- }
1066
- `;
1067
- document.head.appendChild(style);
1068
- }
1069
- function createButton(name, icon, title, onClick) {
1070
- const btn = document.createElement("button");
1071
- btn.innerHTML = icon;
1072
- btn.title = title;
1073
- btn.setAttribute("aria-label", title);
1074
- btn.setAttribute("data-ag-btn", name);
1075
- btn.addEventListener("click", (e) => {
1076
- e.preventDefault();
1077
- e.stopPropagation();
1078
- onClick();
1079
- });
1080
- return btn;
1081
- }
1082
- function ensureContainer() {
1083
- if (container) return;
1084
- injectStyles2();
1085
- container = document.createElement("div");
1086
- container.id = TOOLBAR_ID;
1087
- container.setAttribute("role", "toolbar");
1088
- container.setAttribute("aria-label", "Angular Grab toolbar");
1089
- buttons.selection = createButton("selection", ICON_GRAB, "Selection mode", callbacks.onSelectionMode);
1090
- buttons.history = createButton("history", ICON_HISTORY, "History", callbacks.onHistory);
1091
- buttons.actions = createButton("actions", ICON_ELLIPSIS, "Actions", callbacks.onActions);
1092
- buttons.freeze = createButton("freeze", ICON_FREEZE, "Freeze page (F)", callbacks.onFreeze);
1093
- buttons.theme = createButton("theme", ICON_SUN, "Toggle theme", callbacks.onThemeToggle);
1094
- buttons.enable = createButton("enable", ICON_POWER, "Enable/Disable", callbacks.onEnableToggle);
1095
- buttons.dismiss = createButton("dismiss", ICON_DISMISS, "Dismiss toolbar", callbacks.onDismiss);
1096
- const divider = document.createElement("span");
1097
- divider.className = "ag-toolbar-divider";
1098
- leftGroup = document.createElement("div");
1099
- leftGroup.className = "ag-toolbar-left";
1100
- leftGroup.appendChild(buttons.selection);
1101
- leftGroup.appendChild(buttons.history);
1102
- leftGroup.appendChild(buttons.actions);
1103
- leftGroup.appendChild(buttons.freeze);
1104
- leftGroup.appendChild(divider);
1105
- container.appendChild(leftGroup);
1106
- container.appendChild(buttons.theme);
1107
- container.appendChild(buttons.enable);
1108
- container.appendChild(buttons.dismiss);
1109
- document.body.appendChild(container);
1110
- allElements.clear();
1111
- allElements.add(container);
1112
- allElements.add(leftGroup);
1113
- allElements.add(divider);
1114
- for (const btn of Object.values(buttons)) {
1115
- allElements.add(btn);
1116
- }
1117
- }
1118
- return {
1119
- show() {
1120
- ensureContainer();
1121
- container.classList.remove("ag-toolbar-hidden");
1122
- },
1123
- hide() {
1124
- if (container) {
1125
- container.classList.add("ag-toolbar-hidden");
1126
- }
1127
- },
1128
- update(state) {
1129
- if (!container) return;
1130
- if (state.active) {
1131
- buttons.selection.classList.add("ag-btn-active");
1132
- } else {
1133
- buttons.selection.classList.remove("ag-btn-active");
1134
- }
1135
- const mode = state.toolbar.themeMode;
1136
- const themeIcon = mode === "dark" ? ICON_SUN : mode === "light" ? ICON_MOON : ICON_SYSTEM;
1137
- const themeLabel = mode === "dark" ? "Switch to light mode" : mode === "light" ? "Switch to system theme" : "Switch to dark mode";
1138
- buttons.theme.innerHTML = themeIcon;
1139
- buttons.theme.title = themeLabel;
1140
- buttons.theme.setAttribute("aria-label", themeLabel);
1141
- if (state.frozen) {
1142
- buttons.freeze.classList.add("ag-btn-active");
1143
- } else {
1144
- buttons.freeze.classList.remove("ag-btn-active");
1145
- }
1146
- if (state.options.enabled) {
1147
- buttons.enable.classList.add("ag-btn-active");
1148
- leftGroup?.classList.remove("ag-toolbar-left-hidden");
1149
- } else {
1150
- buttons.enable.classList.remove("ag-btn-active");
1151
- leftGroup?.classList.add("ag-toolbar-left-hidden");
1152
- }
1153
- },
1154
- isToolbarElement(el) {
1155
- if (allElements.has(el)) return true;
1156
- let current = el;
1157
- while (current) {
1158
- if (current === container) return true;
1159
- if (current.id === TOOLBAR_ID) return true;
1160
- current = current.parentElement;
1161
- }
1162
- return false;
1163
- },
1164
- dispose() {
1165
- container?.remove();
1166
- document.getElementById(STYLE_ID3)?.remove();
1167
- container = null;
1168
- leftGroup = null;
1169
- buttons = {};
1170
- allElements.clear();
1171
- }
1172
- };
1173
- }
1174
-
1175
- // src/core/toolbar/history-popover.ts
1176
- var POPOVER_ID = "__ag-history-popover__";
1177
- var STYLE_ID4 = "__ag-history-styles__";
1178
- function formatRelativeTime(timestamp) {
1179
- const diff = Date.now() - timestamp;
1180
- const seconds = Math.floor(diff / 1e3);
1181
- if (seconds < 60) return "just now";
1182
- const minutes = Math.floor(seconds / 60);
1183
- if (minutes < 60) return `${minutes}m ago`;
1184
- const hours = Math.floor(minutes / 60);
1185
- if (hours < 24) return `${hours}h ago`;
1186
- const days = Math.floor(hours / 24);
1187
- return `${days}d ago`;
1188
- }
1189
- function shortPath(filePath) {
1190
- const parts = filePath.split("/");
1191
- return parts[parts.length - 1];
1192
- }
1193
- function buildVsCodeUri(filePath, line, column) {
1194
- let uri = `vscode://file/${encodeURI(filePath)}`;
1195
- if (line != null) uri += `:${line}`;
1196
- if (line != null && column != null) uri += `:${column}`;
1197
- return uri;
1198
- }
1199
- function createHistoryPopover(callbacks) {
1200
- let popover = null;
1201
- let visible = false;
1202
- function injectStyles2() {
1203
- if (document.getElementById(STYLE_ID4)) return;
1204
- const style = document.createElement("style");
1205
- style.id = STYLE_ID4;
1206
- style.textContent = `
1207
- #${POPOVER_ID} {
1208
- position: fixed;
1209
- bottom: ${TOOLBAR_POPOVER_OFFSET};
1210
- left: 50%;
1211
- transform: translateX(-50%);
1212
- z-index: ${Z_INDEX_POPOVER};
1213
- background: var(--ag-popover-bg, #0f172a);
1214
- border: 1px solid var(--ag-popover-border, #1e293b);
1215
- border-radius: 12px;
1216
- box-shadow: 0 8px 24px var(--ag-popover-shadow, rgba(0, 0, 0, 0.5));
1217
- min-width: 320px;
1218
- max-width: 420px;
1219
- max-height: 360px;
1220
- overflow-y: auto;
1221
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1222
- opacity: 0;
1223
- visibility: hidden;
1224
- transition: opacity 0.15s ease, visibility 0.15s ease;
1225
- pointer-events: auto;
1226
- }
1227
- #${POPOVER_ID}.ag-popover-visible {
1228
- opacity: 1;
1229
- visibility: visible;
1230
- }
1231
- #${POPOVER_ID} .ag-history-header {
1232
- padding: 10px 14px 8px;
1233
- font-size: 11px;
1234
- font-weight: 600;
1235
- text-transform: uppercase;
1236
- letter-spacing: 0.05em;
1237
- color: var(--ag-text-muted, #64748b);
1238
- border-bottom: 1px solid var(--ag-popover-border, #1e293b);
1239
- }
1240
- #${POPOVER_ID} .ag-history-empty {
1241
- padding: 24px 14px;
1242
- text-align: center;
1243
- color: var(--ag-text-muted, #64748b);
1244
- font-size: 13px;
1245
- }
1246
- #${POPOVER_ID} .ag-history-item {
1247
- display: flex;
1248
- align-items: center;
1249
- justify-content: space-between;
1250
- gap: 12px;
1251
- padding: 8px 14px;
1252
- cursor: pointer;
1253
- border: none;
1254
- border-bottom: 1px solid var(--ag-popover-border, #1e293b);
1255
- background: transparent;
1256
- width: 100%;
1257
- text-align: left;
1258
- font: inherit;
1259
- color: inherit;
1260
- transition: background 0.1s ease;
1261
- }
1262
- #${POPOVER_ID} .ag-history-item:last-child {
1263
- border-bottom: none;
1264
- }
1265
- #${POPOVER_ID} .ag-history-item:hover {
1266
- background: var(--ag-popover-hover, #1e293b);
1267
- }
1268
- #${POPOVER_ID} .ag-history-info {
1269
- flex: 1;
1270
- min-width: 0;
1271
- }
1272
- #${POPOVER_ID} .ag-history-selector {
1273
- font: 12px/1.3 ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
1274
- color: var(--ag-popover-text, #e2e8f0);
1275
- overflow: hidden;
1276
- text-overflow: ellipsis;
1277
- white-space: nowrap;
1278
- }
1279
- #${POPOVER_ID} .ag-history-meta {
1280
- font-size: 11px;
1281
- color: var(--ag-text-muted, #64748b);
1282
- margin-top: 2px;
1283
- overflow: hidden;
1284
- text-overflow: ellipsis;
1285
- white-space: nowrap;
1286
- }
1287
- #${POPOVER_ID} .ag-history-time {
1288
- font-size: 11px;
1289
- color: var(--ag-text-muted, #64748b);
1290
- flex-shrink: 0;
1291
- }
1292
- #${POPOVER_ID} .ag-history-file-link {
1293
- color: var(--ag-text-muted, #64748b);
1294
- text-decoration: none;
1295
- }
1296
- #${POPOVER_ID} .ag-history-file-link:hover {
1297
- text-decoration: underline;
1298
- color: var(--ag-accent, #3b82f6);
1299
- }
1300
- `;
1301
- document.head.appendChild(style);
1302
- }
1303
- function ensurePopover() {
1304
- if (popover) return popover;
1305
- injectStyles2();
1306
- popover = document.createElement("div");
1307
- popover.id = POPOVER_ID;
1308
- popover.setAttribute("role", "dialog");
1309
- popover.setAttribute("aria-label", "Grab history");
1310
- document.body.appendChild(popover);
1311
- return popover;
1312
- }
1313
- function render(entries) {
1314
- const el = ensurePopover();
1315
- let html = '<div class="ag-history-header">History</div>';
1316
- if (entries.length === 0) {
1317
- html += '<div class="ag-history-empty">No elements grabbed yet</div>';
1318
- } else {
1319
- for (const entry of entries) {
1320
- const selector = escapeHtml(entry.context.selector);
1321
- const comp = entry.context.componentName ? escapeHtml(entry.context.componentName) : "";
1322
- const time = formatRelativeTime(entry.timestamp);
1323
- let meta = comp ? `in ${comp}` : "";
1324
- if (entry.context.filePath) {
1325
- const uri = buildVsCodeUri(entry.context.filePath, entry.context.line, entry.context.column);
1326
- const fileName = escapeHtml(shortPath(entry.context.filePath));
1327
- const sep = meta ? " \u2014 " : "";
1328
- meta += `${sep}<a class="ag-history-file-link" href="${escapeHtml(uri)}" title="Open in VS Code">${fileName}</a>`;
1329
- }
1330
- html += `<button class="ag-history-item" data-ag-history-id="${escapeHtml(entry.id)}" aria-label="Re-copy ${selector}">`;
1331
- html += `<div class="ag-history-info">`;
1332
- html += `<div class="ag-history-selector">${selector}</div>`;
1333
- if (meta) html += `<div class="ag-history-meta">${meta}</div>`;
1334
- html += `</div>`;
1335
- html += `<span class="ag-history-time">${time}</span>`;
1336
- html += `</button>`;
1337
- }
1338
- }
1339
- el.innerHTML = html;
1340
- const items = el.querySelectorAll(".ag-history-item");
1341
- items.forEach((item) => {
1342
- item.addEventListener("click", (e) => {
1343
- e.stopPropagation();
1344
- const id = item.dataset.agHistoryId;
1345
- const entry = entries.find((ent) => ent.id === id);
1346
- if (entry) {
1347
- callbacks.onEntryClick(entry);
1348
- }
1349
- });
1350
- });
1351
- }
1352
- return {
1353
- show(entries) {
1354
- render(entries);
1355
- visible = true;
1356
- void ensurePopover().offsetHeight;
1357
- ensurePopover().classList.add("ag-popover-visible");
1358
- },
1359
- hide() {
1360
- visible = false;
1361
- popover?.classList.remove("ag-popover-visible");
1362
- },
1363
- isVisible() {
1364
- return visible;
1365
- },
1366
- isPopoverElement(el) {
1367
- if (!popover) return false;
1368
- let current = el;
1369
- while (current) {
1370
- if (current === popover || current.id === POPOVER_ID) return true;
1371
- current = current.parentElement;
1372
- }
1373
- return false;
1374
- },
1375
- dispose() {
1376
- popover?.remove();
1377
- document.getElementById(STYLE_ID4)?.remove();
1378
- popover = null;
1379
- visible = false;
1380
- }
1381
- };
1382
- }
1383
-
1384
- // src/core/toolbar/actions-menu.ts
1385
- var MENU_ID = "__ag-actions-menu__";
1386
- var STYLE_ID5 = "__ag-actions-styles__";
1387
- function createActionsMenu(callbacks) {
1388
- let menu = null;
1389
- let visible = false;
1390
- const items = [
1391
- { icon: ICON_COPY, label: "Copy Element", action: callbacks.onCopyElement },
1392
- { icon: ICON_STYLES, label: "Copy Styles", action: callbacks.onCopyStyles },
1393
- { icon: ICON_CODE, label: "Copy HTML", action: callbacks.onCopyHtml },
1394
- { icon: ICON_COMMENT, label: "Comment", action: callbacks.onComment },
1395
- { separator: true },
1396
- { icon: ICON_TRASH, label: "Clear History", action: callbacks.onClearHistory }
1397
- ];
1398
- function injectStyles2() {
1399
- if (document.getElementById(STYLE_ID5)) return;
1400
- const style = document.createElement("style");
1401
- style.id = STYLE_ID5;
1402
- style.textContent = `
1403
- #${MENU_ID} {
1404
- position: fixed;
1405
- bottom: ${TOOLBAR_POPOVER_OFFSET};
1406
- left: 50%;
1407
- transform: translateX(-50%);
1408
- z-index: ${Z_INDEX_POPOVER};
1409
- background: var(--ag-popover-bg, #0f172a);
1410
- border: 1px solid var(--ag-popover-border, #1e293b);
1411
- border-radius: 10px;
1412
- box-shadow: 0 8px 24px var(--ag-popover-shadow, rgba(0, 0, 0, 0.5));
1413
- min-width: 200px;
1414
- padding: 4px;
1415
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1416
- opacity: 0;
1417
- visibility: hidden;
1418
- transition: opacity 0.15s ease, visibility 0.15s ease;
1419
- pointer-events: auto;
1420
- }
1421
- #${MENU_ID}.ag-menu-visible {
1422
- opacity: 1;
1423
- visibility: visible;
1424
- }
1425
- #${MENU_ID} .ag-menu-item {
1426
- display: flex;
1427
- align-items: center;
1428
- gap: 10px;
1429
- padding: 8px 12px;
1430
- border: none;
1431
- border-radius: 6px;
1432
- background: transparent;
1433
- color: var(--ag-popover-text, #e2e8f0);
1434
- font-size: 13px;
1435
- cursor: pointer;
1436
- width: 100%;
1437
- text-align: left;
1438
- transition: background 0.1s ease;
1439
- }
1440
- #${MENU_ID} .ag-menu-item:hover {
1441
- background: var(--ag-popover-hover, #1e293b);
1442
- }
1443
- #${MENU_ID} .ag-menu-item svg {
1444
- flex-shrink: 0;
1445
- opacity: 0.7;
1446
- }
1447
- #${MENU_ID} .ag-menu-sep {
1448
- height: 1px;
1449
- background: var(--ag-popover-border, #1e293b);
1450
- margin: 4px 8px;
1451
- }
1452
- `;
1453
- document.head.appendChild(style);
1454
- }
1455
- function ensureMenu() {
1456
- if (menu) return menu;
1457
- injectStyles2();
1458
- menu = document.createElement("div");
1459
- menu.id = MENU_ID;
1460
- menu.setAttribute("role", "menu");
1461
- menu.setAttribute("aria-label", "Actions");
1462
- for (const entry of items) {
1463
- if (entry.separator) {
1464
- const sep = document.createElement("div");
1465
- sep.className = "ag-menu-sep";
1466
- menu.appendChild(sep);
1467
- continue;
1468
- }
1469
- const btn = document.createElement("button");
1470
- btn.className = "ag-menu-item";
1471
- btn.setAttribute("role", "menuitem");
1472
- btn.innerHTML = `${entry.icon}<span>${escapeHtml(entry.label)}</span>`;
1473
- btn.addEventListener("click", (e) => {
1474
- e.preventDefault();
1475
- e.stopPropagation();
1476
- visible = false;
1477
- menu?.classList.remove("ag-menu-visible");
1478
- entry.action();
1479
- });
1480
- menu.appendChild(btn);
1481
- }
1482
- document.body.appendChild(menu);
1483
- return menu;
1484
- }
1485
- return {
1486
- show() {
1487
- const el = ensureMenu();
1488
- visible = true;
1489
- void el.offsetHeight;
1490
- el.classList.add("ag-menu-visible");
1491
- },
1492
- hide() {
1493
- visible = false;
1494
- menu?.classList.remove("ag-menu-visible");
1495
- },
1496
- isVisible() {
1497
- return visible;
1498
- },
1499
- isMenuElement(el) {
1500
- if (!menu) return false;
1501
- let current = el;
1502
- while (current) {
1503
- if (current === menu || current.id === MENU_ID) return true;
1504
- current = current.parentElement;
1505
- }
1506
- return false;
1507
- },
1508
- dispose() {
1509
- menu?.remove();
1510
- document.getElementById(STYLE_ID5)?.remove();
1511
- menu = null;
1512
- visible = false;
1513
- }
1514
- };
1515
- }
1516
-
1517
- // src/core/toolbar/comment-popover.ts
1518
- var POPOVER_ID2 = "__ag-comment-popover__";
1519
- var STYLE_ID6 = "__ag-comment-styles__";
1520
- function createCommentPopover(callbacks) {
1521
- let popover = null;
1522
- let textarea = null;
1523
- let visible = false;
1524
- let keydownHandler = null;
1525
- function injectStyles2() {
1526
- if (document.getElementById(STYLE_ID6)) return;
1527
- const style = document.createElement("style");
1528
- style.id = STYLE_ID6;
1529
- style.textContent = `
1530
- #${POPOVER_ID2} {
1531
- position: fixed;
1532
- bottom: ${TOOLBAR_POPOVER_OFFSET};
1533
- left: 50%;
1534
- transform: translateX(-50%);
1535
- z-index: ${Z_INDEX_POPOVER};
1536
- background: var(--ag-popover-bg, #0f172a);
1537
- border: 1px solid var(--ag-popover-border, #1e293b);
1538
- border-radius: 12px;
1539
- box-shadow: 0 8px 24px var(--ag-popover-shadow, rgba(0, 0, 0, 0.5));
1540
- width: 340px;
1541
- padding: 14px;
1542
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1543
- opacity: 0;
1544
- visibility: hidden;
1545
- transition: opacity 0.15s ease, visibility 0.15s ease;
1546
- pointer-events: auto;
1547
- }
1548
- #${POPOVER_ID2}.ag-comment-visible {
1549
- opacity: 1;
1550
- visibility: visible;
1551
- }
1552
- #${POPOVER_ID2} textarea {
1553
- width: 100%;
1554
- min-height: 80px;
1555
- padding: 8px 10px;
1556
- border: 1px solid var(--ag-popover-border, #1e293b);
1557
- border-radius: 8px;
1558
- background: var(--ag-surface, #1e293b);
1559
- color: var(--ag-popover-text, #e2e8f0);
1560
- font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1561
- resize: vertical;
1562
- outline: none;
1563
- box-sizing: border-box;
1564
- }
1565
- #${POPOVER_ID2} textarea:focus {
1566
- border-color: var(--ag-accent, #3b82f6);
1567
- }
1568
- #${POPOVER_ID2} textarea::placeholder {
1569
- color: var(--ag-text-muted, #64748b);
1570
- }
1571
- #${POPOVER_ID2} .ag-comment-footer {
1572
- display: flex;
1573
- justify-content: flex-end;
1574
- gap: 8px;
1575
- margin-top: 10px;
1576
- }
1577
- #${POPOVER_ID2} .ag-comment-btn {
1578
- padding: 6px 14px;
1579
- border: none;
1580
- border-radius: 6px;
1581
- font-size: 13px;
1582
- font-weight: 500;
1583
- cursor: pointer;
1584
- transition: background 0.15s ease;
1585
- }
1586
- #${POPOVER_ID2} .ag-comment-cancel {
1587
- background: transparent;
1588
- color: var(--ag-text-muted, #64748b);
1589
- }
1590
- #${POPOVER_ID2} .ag-comment-cancel:hover {
1591
- background: var(--ag-popover-hover, #1e293b);
1592
- color: var(--ag-popover-text, #e2e8f0);
1593
- }
1594
- #${POPOVER_ID2} .ag-comment-submit {
1595
- background: var(--ag-accent, #3b82f6);
1596
- color: #fff;
1597
- }
1598
- #${POPOVER_ID2} .ag-comment-submit:hover {
1599
- background: var(--ag-accent-hover, #2563eb);
1600
- }
1601
- `;
1602
- document.head.appendChild(style);
1603
- }
1604
- function ensurePopover() {
1605
- if (popover) return popover;
1606
- injectStyles2();
1607
- popover = document.createElement("div");
1608
- popover.id = POPOVER_ID2;
1609
- popover.setAttribute("role", "dialog");
1610
- popover.setAttribute("aria-label", "Add comment");
1611
- textarea = document.createElement("textarea");
1612
- textarea.placeholder = "Add a comment...";
1613
- textarea.rows = 3;
1614
- const footer = document.createElement("div");
1615
- footer.className = "ag-comment-footer";
1616
- const cancelBtn = document.createElement("button");
1617
- cancelBtn.className = "ag-comment-btn ag-comment-cancel";
1618
- cancelBtn.textContent = "Cancel";
1619
- cancelBtn.addEventListener("click", (e) => {
1620
- e.preventDefault();
1621
- e.stopPropagation();
1622
- doHide();
1623
- callbacks.onCancel();
1624
- });
1625
- const submitBtn = document.createElement("button");
1626
- submitBtn.className = "ag-comment-btn ag-comment-submit";
1627
- submitBtn.textContent = "Copy with Comment";
1628
- submitBtn.addEventListener("click", (e) => {
1629
- e.preventDefault();
1630
- e.stopPropagation();
1631
- const comment = textarea.value.trim();
1632
- if (comment) {
1633
- doHide();
1634
- callbacks.onSubmit(comment);
1635
- } else {
1636
- textarea.style.borderColor = "var(--ag-accent, #3b82f6)";
1637
- textarea.setAttribute("placeholder", "Please enter a comment...");
1638
- textarea.focus();
1639
- setTimeout(() => {
1640
- if (textarea) {
1641
- textarea.style.borderColor = "";
1642
- textarea.setAttribute("placeholder", "Add a comment...");
1643
- }
1644
- }, 2e3);
1645
- }
1646
- });
1647
- footer.appendChild(cancelBtn);
1648
- footer.appendChild(submitBtn);
1649
- popover.appendChild(textarea);
1650
- popover.appendChild(footer);
1651
- document.body.appendChild(popover);
1652
- return popover;
1653
- }
1654
- function attachKeydownInterceptor() {
1655
- if (keydownHandler) return;
1656
- keydownHandler = (e) => {
1657
- if (textarea && document.activeElement === textarea) {
1658
- e.stopImmediatePropagation();
1659
- if (e.key === "Escape") {
1660
- doHide();
1661
- callbacks.onCancel();
1662
- }
1663
- }
1664
- };
1665
- document.addEventListener("keydown", keydownHandler, true);
1666
- }
1667
- function detachKeydownInterceptor() {
1668
- if (keydownHandler) {
1669
- document.removeEventListener("keydown", keydownHandler, true);
1670
- keydownHandler = null;
1671
- }
1672
- }
1673
- function doHide() {
1674
- visible = false;
1675
- popover?.classList.remove("ag-comment-visible");
1676
- detachKeydownInterceptor();
1677
- }
1678
- return {
1679
- show() {
1680
- const el = ensurePopover();
1681
- textarea.value = "";
1682
- visible = true;
1683
- void el.offsetHeight;
1684
- el.classList.add("ag-comment-visible");
1685
- attachKeydownInterceptor();
1686
- requestAnimationFrame(() => textarea?.focus());
1687
- },
1688
- hide() {
1689
- doHide();
1690
- },
1691
- isVisible() {
1692
- return visible;
1693
- },
1694
- isPopoverElement(el) {
1695
- if (!popover) return false;
1696
- let current = el;
1697
- while (current) {
1698
- if (current === popover || current.id === POPOVER_ID2) return true;
1699
- current = current.parentElement;
1700
- }
1701
- return false;
1702
- },
1703
- dispose() {
1704
- detachKeydownInterceptor();
1705
- popover?.remove();
1706
- document.getElementById(STYLE_ID6)?.remove();
1707
- popover = null;
1708
- textarea = null;
1709
- visible = false;
1710
- }
1711
- };
1712
- }
1713
-
1714
- // src/core/toolbar/copy-actions.ts
1715
- async function copyElementSnippet(context, maxLines, pluginRegistry) {
1716
- let snippet = generateSnippet(context, maxLines);
1717
- if (pluginRegistry) {
1718
- snippet = pluginRegistry.callTransformHook(snippet, context);
1719
- }
1720
- const ok = await writeAndToast(snippet, "Copied to clipboard", context);
1721
- if (ok) pluginRegistry?.callHook("onCopySuccess", snippet, context, void 0);
1722
- return ok;
1723
- }
1724
- async function copyElementHtml(context, pluginRegistry) {
1725
- const cleaned = cleanAngularAttrs(context.html);
1726
- const ok = await writeAndToast(cleaned, "HTML copied to clipboard", context);
1727
- if (ok) pluginRegistry?.callHook("onCopySuccess", cleaned, context, void 0);
1728
- return ok;
1729
- }
1730
- async function copyElementStyles(element) {
1731
- if (!element.isConnected) {
1732
- showToast("Element is no longer on the page");
1733
- return false;
1734
- }
1735
- const computed = window.getComputedStyle(element);
1736
- const tag = element.tagName.toLowerCase();
1737
- const lines = [`/* Computed styles for <${tag}> */`, `${tag} {`];
1738
- const props = [
1739
- "display",
1740
- "position",
1741
- "top",
1742
- "right",
1743
- "bottom",
1744
- "left",
1745
- "width",
1746
- "height",
1747
- "min-width",
1748
- "min-height",
1749
- "max-width",
1750
- "max-height",
1751
- "margin",
1752
- "padding",
1753
- "border",
1754
- "border-radius",
1755
- "background",
1756
- "background-color",
1757
- "color",
1758
- "font",
1759
- "font-size",
1760
- "font-weight",
1761
- "font-family",
1762
- "line-height",
1763
- "text-align",
1764
- "text-decoration",
1765
- "text-transform",
1766
- "opacity",
1767
- "overflow",
1768
- "z-index",
1769
- "flex-direction",
1770
- "justify-content",
1771
- "align-items",
1772
- "gap",
1773
- "grid-template-columns",
1774
- "grid-template-rows",
1775
- "box-shadow",
1776
- "cursor",
1777
- "transition",
1778
- "transform"
1779
- ];
1780
- for (const prop of props) {
1781
- const value = computed.getPropertyValue(prop);
1782
- if (value && value !== "none" && value !== "normal" && value !== "auto" && value !== "0px" && value !== "visible") {
1783
- lines.push(` ${prop}: ${value};`);
1784
- }
1785
- }
1786
- lines.push("}");
1787
- const css = lines.join("\n");
1788
- return writeAndToast(css, "Styles copied to clipboard");
1789
- }
1790
- async function copyWithComment(context, comment, maxLines, pluginRegistry) {
1791
- let snippet = generateSnippet(context, maxLines);
1792
- if (pluginRegistry) {
1793
- snippet = pluginRegistry.callTransformHook(snippet, context);
1794
- }
1795
- const full = `${snippet}
1796
-
1797
- /* Comment: ${comment} */`;
1798
- const ok = await writeAndToast(full, "Copied with comment", context);
1799
- if (ok) pluginRegistry?.callHook("onCopySuccess", full, context, comment);
1800
- return ok;
1801
- }
1802
- async function writeAndToast(text, message, context) {
1803
- try {
1804
- await navigator.clipboard.writeText(text);
1805
- showToast(message, context ? {
1806
- componentName: context.componentName,
1807
- filePath: context.filePath,
1808
- line: context.line,
1809
- column: context.column,
1810
- cssClasses: context.cssClasses
1811
- } : void 0);
1812
- return true;
1813
- } catch {
1814
- return false;
1815
- }
1816
- }
1817
-
1818
- // src/core/overlay/freeze-overlay.ts
1819
- var FREEZE_ID = "__ag-freeze-overlay__";
1820
- var FREEZE_STYLE_ID = "__ag-freeze-styles__";
1821
- var HOVER_STYLE_ID = "__ag-freeze-hover-styles__";
1822
- var ANIM_STYLE_ID = "__ag-freeze-anim-styles__";
1823
- var HOVER_ATTR = "data-ag-hover";
1824
- var MOUSE_EVENTS_TO_BLOCK = [
1825
- "mouseenter",
1826
- "mouseleave",
1827
- "mouseover",
1828
- "mouseout",
1829
- "pointerenter",
1830
- "pointerleave",
1831
- "pointerover",
1832
- "pointerout"
1833
- ];
1834
- var FOCUS_EVENTS_TO_BLOCK = ["focus", "blur", "focusin", "focusout"];
1835
- function createFreezeOverlay() {
1836
- let overlay = null;
1837
- let visible = false;
1838
- let hoverStyleEl = null;
1839
- let animStyleEl = null;
1840
- let markedElements = [];
1841
- function injectStyles2() {
1842
- if (document.getElementById(FREEZE_STYLE_ID)) return;
1843
- const style = document.createElement("style");
1844
- style.id = FREEZE_STYLE_ID;
1845
- style.textContent = `
1846
- #${FREEZE_ID} {
1847
- position: fixed;
1848
- top: 0;
1849
- left: 0;
1850
- width: 100vw;
1851
- height: 100vh;
1852
- z-index: ${Z_INDEX_FREEZE};
1853
- pointer-events: auto;
1854
- background: transparent;
1855
- }
1856
- `;
1857
- document.head.appendChild(style);
1858
- }
1859
- function ensureOverlay() {
1860
- if (overlay) return overlay;
1861
- injectStyles2();
1862
- overlay = document.createElement("div");
1863
- overlay.id = FREEZE_ID;
1864
- overlay.style.display = "none";
1865
- document.body.appendChild(overlay);
1866
- return overlay;
1867
- }
1868
- const stopEvent = (e) => {
1869
- e.stopImmediatePropagation();
1870
- };
1871
- const preventFocusChange = (e) => {
1872
- e.preventDefault();
1873
- e.stopImmediatePropagation();
1874
- };
1875
- function blockEvents() {
1876
- for (const type of MOUSE_EVENTS_TO_BLOCK) {
1877
- document.addEventListener(type, stopEvent, true);
1878
- }
1879
- for (const type of FOCUS_EVENTS_TO_BLOCK) {
1880
- document.addEventListener(type, preventFocusChange, true);
1881
- }
1882
- }
1883
- function unblockEvents() {
1884
- for (const type of MOUSE_EVENTS_TO_BLOCK) {
1885
- document.removeEventListener(type, stopEvent, true);
1886
- }
1887
- for (const type of FOCUS_EVENTS_TO_BLOCK) {
1888
- document.removeEventListener(type, preventFocusChange, true);
1889
- }
1890
- }
1891
- function markHoverChain(element) {
1892
- let current = element;
1893
- while (current && current !== document.documentElement) {
1894
- current.setAttribute(HOVER_ATTR, "");
1895
- markedElements.push(current);
1896
- current = current.parentElement;
1897
- }
1898
- }
1899
- function clearHoverMarks() {
1900
- for (const el of markedElements) {
1901
- el.removeAttribute(HOVER_ATTR);
1902
- }
1903
- markedElements = [];
1904
- }
1905
- function injectHoverRules() {
1906
- if (hoverStyleEl) return;
1907
- const cloned = [];
1908
- for (const sheet of Array.from(document.styleSheets)) {
1909
- let rules;
1910
- try {
1911
- rules = sheet.cssRules;
1912
- } catch {
1913
- continue;
1914
- }
1915
- collectHoverRules(rules, cloned);
1916
- }
1917
- if (cloned.length === 0) return;
1918
- hoverStyleEl = document.createElement("style");
1919
- hoverStyleEl.id = HOVER_STYLE_ID;
1920
- hoverStyleEl.textContent = cloned.join("\n");
1921
- document.head.appendChild(hoverStyleEl);
1922
- }
1923
- function collectHoverRules(rules, out) {
1924
- for (const rule of Array.from(rules)) {
1925
- if (rule instanceof CSSStyleRule) {
1926
- if (rule.selectorText.includes(":hover")) {
1927
- const newSelector = rule.selectorText.replace(/:hover/g, `[${HOVER_ATTR}]`);
1928
- out.push(`${newSelector} { ${rule.style.cssText} }`);
1929
- }
1930
- } else if (rule instanceof CSSMediaRule) {
1931
- const inner = [];
1932
- collectHoverRules(rule.cssRules, inner);
1933
- if (inner.length > 0) {
1934
- out.push(`@media ${rule.conditionText} { ${inner.join("\n")} }`);
1935
- }
1936
- }
1937
- }
1938
- }
1939
- function removeHoverRules() {
1940
- hoverStyleEl?.remove();
1941
- hoverStyleEl = null;
1942
- }
1943
- function freezeAnimations() {
1944
- if (animStyleEl) return;
1945
- animStyleEl = document.createElement("style");
1946
- animStyleEl.id = ANIM_STYLE_ID;
1947
- animStyleEl.textContent = `
1948
- *, *::before, *::after {
1949
- animation-play-state: paused !important;
1950
- transition: none !important;
1951
- }
1952
- `;
1953
- document.head.appendChild(animStyleEl);
1954
- }
1955
- function unfreezeAnimations() {
1956
- animStyleEl?.remove();
1957
- animStyleEl = null;
1958
- }
1959
- return {
1960
- show(hoveredElement) {
1961
- blockEvents();
1962
- if (hoveredElement) {
1963
- markHoverChain(hoveredElement);
1964
- injectHoverRules();
1965
- }
1966
- freezeAnimations();
1967
- const el = ensureOverlay();
1968
- el.style.display = "block";
1969
- visible = true;
1970
- },
1971
- hide() {
1972
- if (overlay) overlay.style.display = "none";
1973
- visible = false;
1974
- clearHoverMarks();
1975
- removeHoverRules();
1976
- unfreezeAnimations();
1977
- unblockEvents();
1978
- },
1979
- isVisible() {
1980
- return visible;
1981
- },
1982
- isFreezeElement(el) {
1983
- return el === overlay || el.id === FREEZE_ID;
1984
- },
1985
- getElement() {
1986
- return overlay;
1987
- },
1988
- dispose() {
1989
- clearHoverMarks();
1990
- removeHoverRules();
1991
- unfreezeAnimations();
1992
- unblockEvents();
1993
- overlay?.remove();
1994
- document.getElementById(FREEZE_STYLE_ID)?.remove();
1995
- overlay = null;
1996
- visible = false;
1997
- }
1998
- };
1999
- }
2000
-
2001
- // src/core/overlay/select-feedback.ts
2002
- var STYLE_ID7 = "__ag-feedback-styles__";
2003
- function injectStyles() {
2004
- if (document.getElementById(STYLE_ID7)) return;
2005
- const style = document.createElement("style");
2006
- style.id = STYLE_ID7;
2007
- style.textContent = `
2008
- @keyframes ag-flash {
2009
- 0% { opacity: 1; }
2010
- 100% { opacity: 0; transform: scale(1.02); }
2011
- }
2012
- @keyframes ag-pill-in {
2013
- 0% { opacity: 0; transform: translateY(4px) scale(0.9); }
2014
- 30% { opacity: 1; transform: translateY(0) scale(1); }
2015
- 70% { opacity: 1; transform: translateY(0) scale(1); }
2016
- 100% { opacity: 0; transform: translateY(-8px) scale(0.95); }
2017
- }
2018
- .ag-select-flash {
2019
- position: fixed;
2020
- pointer-events: none;
2021
- z-index: ${Z_INDEX_OVERLAY};
2022
- border: 2px solid #22c55e;
2023
- background: rgba(34, 197, 94, 0.12);
2024
- border-radius: 3px;
2025
- box-sizing: border-box;
2026
- animation: ag-flash 0.45s ease-out forwards;
2027
- }
2028
- .ag-select-pill {
2029
- position: fixed;
2030
- pointer-events: none;
2031
- z-index: ${Z_INDEX_LABEL};
2032
- display: flex;
2033
- align-items: center;
2034
- gap: 4px;
2035
- background: #22c55e;
2036
- color: #fff;
2037
- font: 600 10px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
2038
- padding: 3px 8px;
2039
- border-radius: 10px;
2040
- white-space: nowrap;
2041
- letter-spacing: 0.03em;
2042
- text-transform: uppercase;
2043
- box-shadow: 0 2px 8px rgba(34, 197, 94, 0.35);
2044
- animation: ag-pill-in 0.9s ease-out forwards;
2045
- }
2046
- .ag-select-pill svg {
2047
- width: 10px;
2048
- height: 10px;
2049
- flex-shrink: 0;
2050
- }
2051
- `;
2052
- document.head.appendChild(style);
2053
- }
2054
- var CHECK_SVG = `<svg viewBox="0 0 10 10" fill="none"><path d="M2 5.5l2 2 4-4" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
2055
- function showSelectFeedback(element) {
2056
- injectStyles();
2057
- const rect = element.getBoundingClientRect();
2058
- const flash = document.createElement("div");
2059
- flash.className = "ag-select-flash";
2060
- flash.style.top = `${rect.top}px`;
2061
- flash.style.left = `${rect.left}px`;
2062
- flash.style.width = `${rect.width}px`;
2063
- flash.style.height = `${rect.height}px`;
2064
- document.body.appendChild(flash);
2065
- const pill = document.createElement("div");
2066
- pill.className = "ag-select-pill";
2067
- pill.innerHTML = `${CHECK_SVG} Copied`;
2068
- document.body.appendChild(pill);
2069
- const pillWidth = 70;
2070
- let pillLeft = rect.left + rect.width / 2 - pillWidth / 2;
2071
- let pillTop = rect.top - 24;
2072
- if (pillTop < 4) {
2073
- pillTop = rect.bottom + 6;
2074
- }
2075
- const vw = document.documentElement.clientWidth;
2076
- if (pillLeft + pillWidth > vw - 4) pillLeft = vw - pillWidth - 4;
2077
- if (pillLeft < 4) pillLeft = 4;
2078
- pill.style.top = `${pillTop}px`;
2079
- pill.style.left = `${pillLeft}px`;
2080
- const cleanup = () => {
2081
- flash.remove();
2082
- pill.remove();
2083
- };
2084
- pill.addEventListener("animationend", cleanup);
2085
- setTimeout(cleanup, 1200);
2086
- }
2087
- function disposeFeedbackStyles() {
2088
- document.getElementById(STYLE_ID7)?.remove();
2089
- }
2090
-
2091
- // src/core/grab.ts
2092
- var MAX_HISTORY = 50;
2093
- function toHistoryContext(ctx) {
2094
- return {
2095
- html: ctx.html,
2096
- componentName: ctx.componentName,
2097
- filePath: ctx.filePath,
2098
- line: ctx.line,
2099
- column: ctx.column,
2100
- componentStack: ctx.componentStack,
2101
- selector: ctx.selector,
2102
- cssClasses: ctx.cssClasses
2103
- };
2104
- }
2105
- function getDefaultOptions() {
2106
- return {
2107
- activationKey: isMac() ? "Meta+C" : "Ctrl+C",
2108
- activationMode: "hold",
2109
- keyHoldDuration: 0,
2110
- maxContextLines: 20,
2111
- enabled: true,
2112
- enableInInputs: false,
2113
- devOnly: true,
2114
- showToolbar: true,
2115
- themeMode: "dark"
2116
- };
2117
- }
2118
- function init(options) {
2119
- return createGrabInstance(options);
2120
- }
2121
- function isDevMode() {
2122
- try {
2123
- const ng = globalThis.ngDevMode;
2124
- return typeof ng === "undefined" || !!ng;
2125
- } catch {
2126
- return true;
2127
- }
2128
- }
2129
- function createNoopApi() {
2130
- const noop = () => {
2131
- };
2132
- return {
2133
- activate: noop,
2134
- deactivate: noop,
2135
- toggle: noop,
2136
- isActive: () => false,
2137
- setOptions: noop,
2138
- registerPlugin: noop,
2139
- unregisterPlugin: noop,
2140
- setComponentResolver: noop,
2141
- setSourceResolver: noop,
2142
- showToolbar: noop,
2143
- hideToolbar: noop,
2144
- setThemeMode: noop,
2145
- getHistory: () => [],
2146
- clearHistory: noop,
2147
- dispose: noop
2148
- };
2149
- }
2150
- function createGrabInstance(options) {
2151
- const defaults = getDefaultOptions();
2152
- const merged = { ...defaults, ...options };
2153
- if (merged.devOnly && !isDevMode()) {
2154
- return createNoopApi();
2155
- }
2156
- const store = createStore(merged);
2157
- const overlay = createOverlayRenderer();
2158
- const crosshair = createCrosshair();
2159
- const freezeOverlay = createFreezeOverlay();
2160
- const pluginRegistry = createPluginRegistry();
2161
- const themeManager = createThemeManager();
2162
- let componentResolver = null;
2163
- let sourceResolver = null;
2164
- let lastSelectedElement = null;
2165
- let lastSelectedContext = null;
2166
- let idCounter = 0;
2167
- function nextId() {
2168
- return `ag-${++idCounter}-${Date.now()}`;
2169
- }
2170
- themeManager.apply(store.state.toolbar.themeMode);
2171
- updateToastOffset();
2172
- function isAnyToolbarElement(el) {
2173
- return toolbar.isToolbarElement(el) || historyPopover.isPopoverElement(el) || actionsMenu.isMenuElement(el) || commentPopover.isPopoverElement(el) || freezeOverlay.isFreezeElement(el);
2174
- }
2175
- function addHistoryEntry(context, snippet) {
2176
- const entry = {
2177
- id: nextId(),
2178
- context: toHistoryContext(context),
2179
- snippet,
2180
- timestamp: Date.now()
2181
- };
2182
- lastSelectedElement = new WeakRef(context.element);
2183
- lastSelectedContext = context;
2184
- const history = [entry, ...store.state.toolbar.history].slice(0, MAX_HISTORY);
2185
- store.state.toolbar = { ...store.state.toolbar, history };
2186
- }
2187
- function getLastSelectedElement() {
2188
- const el = lastSelectedElement?.deref() ?? null;
2189
- if (el && !el.isConnected) {
2190
- lastSelectedElement = null;
2191
- lastSelectedContext = null;
2192
- return null;
2193
- }
2194
- return el;
2195
- }
2196
- function closeAllPopovers() {
2197
- historyPopover.hide();
2198
- actionsMenu.hide();
2199
- commentPopover.hide();
2200
- }
2201
- async function executePendingAction(pending, element) {
2202
- const context = buildElementContext(element, componentResolver, sourceResolver);
2203
- const maxLines = store.state.options.maxContextLines;
2204
- lastSelectedElement = new WeakRef(element);
2205
- lastSelectedContext = context;
2206
- store.state.toolbar = { ...store.state.toolbar, pendingAction: null };
2207
- switch (pending.type) {
2208
- case "copy-element": {
2209
- const ok = await copyElementSnippet(context, maxLines, pluginRegistry);
2210
- if (ok) {
2211
- showSelectFeedback(element);
2212
- addHistoryEntry(context, "");
2213
- }
2214
- break;
2215
- }
2216
- case "copy-styles":
2217
- await copyElementStyles(element);
2218
- break;
2219
- case "copy-html":
2220
- await copyElementHtml(context, pluginRegistry);
2221
- break;
2222
- case "comment":
2223
- commentPopover.show();
2224
- return;
2225
- }
2226
- }
2227
- const picker = createElementPicker({
2228
- overlay,
2229
- crosshair,
2230
- getComponentResolver: () => componentResolver,
2231
- getSourceResolver: () => sourceResolver,
2232
- isToolbarElement: isAnyToolbarElement,
2233
- getFreezeElement: () => freezeOverlay.getElement(),
2234
- onHover(element) {
2235
- store.state.hoveredElement = element;
2236
- if (element) {
2237
- pluginRegistry.callHook("onElementHover", element);
2238
- }
2239
- },
2240
- async onSelect(element) {
2241
- const pending = store.state.toolbar.pendingAction;
2242
- if (pending) {
2243
- await executePendingAction(pending, element);
2244
- if (pending.type !== "comment") {
2245
- doDeactivate();
2246
- }
2247
- return;
2248
- }
2249
- const result = await copyElement(element, {
2250
- getComponentResolver: () => componentResolver,
2251
- getSourceResolver: () => sourceResolver,
2252
- getMaxContextLines: () => store.state.options.maxContextLines,
2253
- pluginRegistry
2254
- });
2255
- if (result) {
2256
- showSelectFeedback(element);
2257
- addHistoryEntry(result.context, result.snippet);
2258
- }
2259
- }
2260
- });
2261
- function doActivate() {
2262
- if (!store.state.options.enabled) return;
2263
- if (store.state.active) return;
2264
- if (store.state.toolbar.visible === false && store.state.options.showToolbar) {
2265
- store.state.toolbar = { ...store.state.toolbar, visible: true };
2266
- toolbar.show();
2267
- toolbar.update(store.state);
2268
- }
2269
- store.state.active = true;
2270
- picker.activate();
2271
- pluginRegistry.callHook("onActivate");
2272
- toolbar.update(store.state);
2273
- }
2274
- function doDeactivate() {
2275
- if (!store.state.active) return;
2276
- store.state.active = false;
2277
- store.state.frozen = false;
2278
- freezeOverlay.hide();
2279
- store.state.toolbar = { ...store.state.toolbar, pendingAction: null };
2280
- picker.deactivate();
2281
- pluginRegistry.callHook("onDeactivate");
2282
- toolbar.update(store.state);
2283
- }
2284
- function toggleFreeze() {
2285
- store.state.frozen = !store.state.frozen;
2286
- if (store.state.frozen) {
2287
- freezeOverlay.show(store.state.hoveredElement);
2288
- } else {
2289
- freezeOverlay.hide();
2290
- }
2291
- toolbar.update(store.state);
2292
- }
2293
- const toolbar = createToolbarRenderer({
2294
- onSelectionMode() {
2295
- closeAllPopovers();
2296
- if (store.state.active) {
2297
- doDeactivate();
2298
- } else {
2299
- doActivate();
2300
- }
2301
- },
2302
- onHistory() {
2303
- actionsMenu.hide();
2304
- commentPopover.hide();
2305
- if (historyPopover.isVisible()) {
2306
- historyPopover.hide();
2307
- } else {
2308
- historyPopover.show([...store.state.toolbar.history]);
2309
- }
2310
- },
2311
- onActions() {
2312
- historyPopover.hide();
2313
- commentPopover.hide();
2314
- if (actionsMenu.isVisible()) {
2315
- actionsMenu.hide();
2316
- } else {
2317
- actionsMenu.show();
2318
- }
2319
- },
2320
- onFreeze() {
2321
- closeAllPopovers();
2322
- if (!store.state.active) {
2323
- doActivate();
2324
- }
2325
- toggleFreeze();
2326
- },
2327
- onThemeToggle() {
2328
- const current = store.state.toolbar.themeMode;
2329
- const newMode = current === "dark" ? "light" : current === "light" ? "system" : "dark";
2330
- store.state.toolbar = { ...store.state.toolbar, themeMode: newMode };
2331
- themeManager.apply(newMode);
2332
- toolbar.update(store.state);
2333
- },
2334
- onEnableToggle() {
2335
- closeAllPopovers();
2336
- const newEnabled = !store.state.options.enabled;
2337
- store.state.options = { ...store.state.options, enabled: newEnabled };
2338
- if (!newEnabled) {
2339
- doDeactivate();
2340
- }
2341
- toolbar.update(store.state);
2342
- },
2343
- onDismiss() {
2344
- closeAllPopovers();
2345
- doDeactivate();
2346
- store.state.toolbar = { ...store.state.toolbar, visible: false };
2347
- toolbar.hide();
2348
- }
2349
- });
2350
- const historyPopover = createHistoryPopover({
2351
- async onEntryClick(entry) {
2352
- historyPopover.hide();
2353
- try {
2354
- await navigator.clipboard.writeText(entry.snippet);
2355
- showToast("Re-copied to clipboard", {
2356
- componentName: entry.context.componentName,
2357
- filePath: entry.context.filePath,
2358
- line: entry.context.line,
2359
- column: entry.context.column,
2360
- cssClasses: entry.context.cssClasses
2361
- });
2362
- } catch {
2363
- }
2364
- }
2365
- });
2366
- const actionsMenu = createActionsMenu({
2367
- onCopyElement() {
2368
- if (lastSelectedContext) {
2369
- copyElementSnippet(lastSelectedContext, store.state.options.maxContextLines, pluginRegistry);
2370
- } else {
2371
- store.state.toolbar = { ...store.state.toolbar, pendingAction: { type: "copy-element" } };
2372
- doActivate();
2373
- }
2374
- },
2375
- onCopyStyles() {
2376
- const el = getLastSelectedElement();
2377
- if (el) {
2378
- copyElementStyles(el);
2379
- } else {
2380
- store.state.toolbar = { ...store.state.toolbar, pendingAction: { type: "copy-styles" } };
2381
- doActivate();
2382
- }
2383
- },
2384
- onCopyHtml() {
2385
- if (lastSelectedContext) {
2386
- copyElementHtml(lastSelectedContext, pluginRegistry);
2387
- } else {
2388
- store.state.toolbar = { ...store.state.toolbar, pendingAction: { type: "copy-html" } };
2389
- doActivate();
2390
- }
2391
- },
2392
- onComment() {
2393
- if (lastSelectedContext) {
2394
- commentPopover.show();
2395
- } else {
2396
- store.state.toolbar = { ...store.state.toolbar, pendingAction: { type: "comment" } };
2397
- doActivate();
2398
- }
2399
- },
2400
- onClearHistory() {
2401
- lastSelectedContext = null;
2402
- lastSelectedElement = null;
2403
- store.state.toolbar = { ...store.state.toolbar, history: [] };
2404
- }
2405
- });
2406
- const commentPopover = createCommentPopover({
2407
- async onSubmit(comment) {
2408
- if (lastSelectedContext) {
2409
- await copyWithComment(lastSelectedContext, comment, store.state.options.maxContextLines, pluginRegistry);
2410
- }
2411
- if (store.state.active) {
2412
- doDeactivate();
2413
- }
2414
- },
2415
- onCancel() {
2416
- if (store.state.active) {
2417
- doDeactivate();
2418
- }
2419
- }
2420
- });
2421
- function handleDocumentClick(e) {
2422
- const target = e.target;
2423
- if (!target) return;
2424
- if (isAnyToolbarElement(target)) return;
2425
- if (historyPopover.isVisible() || actionsMenu.isVisible() || commentPopover.isVisible()) {
2426
- closeAllPopovers();
2427
- }
2428
- }
2429
- document.addEventListener("click", handleDocumentClick);
2430
- function updateToastOffset() {
2431
- if (store.state.toolbar.visible) {
2432
- document.documentElement.style.setProperty("--ag-toast-bottom", TOOLBAR_TOAST_OFFSET);
2433
- } else {
2434
- document.documentElement.style.removeProperty("--ag-toast-bottom");
2435
- }
2436
- }
2437
- function handleFreezeKey(e) {
2438
- if (!store.state.active) return;
2439
- if (e.key.toLowerCase() !== "f") return;
2440
- const tag = e.target?.tagName;
2441
- if (tag === "INPUT" || tag === "TEXTAREA") return;
2442
- if (e.target?.isContentEditable) return;
2443
- e.preventDefault();
2444
- toggleFreeze();
2445
- }
2446
- document.addEventListener("keydown", handleFreezeKey, true);
2447
- const keyboard = createKeyboardHandler({
2448
- getActivationKey: () => store.state.options.activationKey,
2449
- getActivationMode: () => store.state.options.activationMode,
2450
- getKeyHoldDuration: () => store.state.options.keyHoldDuration,
2451
- getEnableInInputs: () => store.state.options.enableInInputs,
2452
- onActivate: doActivate,
2453
- onDeactivate: doDeactivate,
2454
- isActive: () => store.state.active
2455
- });
2456
- const api = {
2457
- activate: doActivate,
2458
- deactivate: doDeactivate,
2459
- toggle() {
2460
- if (store.state.active) {
2461
- doDeactivate();
2462
- } else {
2463
- doActivate();
2464
- }
2465
- },
2466
- isActive() {
2467
- return store.state.active;
2468
- },
2469
- setOptions(opts) {
2470
- store.state.options = { ...store.state.options, ...opts };
2471
- },
2472
- registerPlugin(plugin) {
2473
- if (plugin.options) {
2474
- store.state.options = { ...store.state.options, ...plugin.options };
2475
- }
2476
- if (plugin.theme) {
2477
- themeManager.applyOverrides(plugin.theme);
2478
- }
2479
- pluginRegistry.register(plugin, api);
2480
- },
2481
- unregisterPlugin(name) {
2482
- pluginRegistry.unregister(name);
2483
- },
2484
- setComponentResolver(resolver) {
2485
- componentResolver = resolver;
2486
- },
2487
- setSourceResolver(resolver) {
2488
- sourceResolver = resolver;
2489
- },
2490
- showToolbar() {
2491
- store.state.toolbar = { ...store.state.toolbar, visible: true };
2492
- toolbar.show();
2493
- toolbar.update(store.state);
2494
- updateToastOffset();
2495
- },
2496
- hideToolbar() {
2497
- closeAllPopovers();
2498
- store.state.toolbar = { ...store.state.toolbar, visible: false };
2499
- toolbar.hide();
2500
- updateToastOffset();
2501
- },
2502
- setThemeMode(mode) {
2503
- store.state.toolbar = { ...store.state.toolbar, themeMode: mode };
2504
- themeManager.apply(mode);
2505
- toolbar.update(store.state);
2506
- },
2507
- getHistory() {
2508
- return [...store.state.toolbar.history];
2509
- },
2510
- clearHistory() {
2511
- lastSelectedContext = null;
2512
- lastSelectedElement = null;
2513
- store.state.toolbar = { ...store.state.toolbar, history: [] };
2514
- },
2515
- dispose() {
2516
- doDeactivate();
2517
- document.removeEventListener("click", handleDocumentClick);
2518
- document.removeEventListener("keydown", handleFreezeKey, true);
2519
- keyboard.dispose();
2520
- picker.dispose();
2521
- overlay.dispose();
2522
- crosshair.dispose();
2523
- freezeOverlay.dispose();
2524
- disposeToast();
2525
- disposeFeedbackStyles();
2526
- pluginRegistry.dispose();
2527
- closeAllPopovers();
2528
- toolbar.dispose();
2529
- historyPopover.dispose();
2530
- actionsMenu.dispose();
2531
- commentPopover.dispose();
2532
- themeManager.dispose();
2533
- document.documentElement.style.removeProperty("--ag-toast-bottom");
2534
- }
2535
- };
2536
- if (store.state.options.enabled) {
2537
- keyboard.start();
2538
- }
2539
- store.state.toolbar = { ...store.state.toolbar, visible: false };
2540
- store.subscribe((state, key) => {
2541
- if (key === "options") {
2542
- if (state.options.enabled) {
2543
- keyboard.start();
2544
- } else {
2545
- keyboard.stop();
2546
- doDeactivate();
2547
- }
2548
- }
2549
- if (key === "toolbar") {
2550
- updateToastOffset();
2551
- }
2552
- });
2553
- return api;
2554
- }
2555
- export {
2556
- createGrabInstance,
2557
- filterAngularClasses,
2558
- init
2559
- };
2560
- //# sourceMappingURL=index.js.map