@ng-annotate/angular 0.2.1 → 0.2.3

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.
@@ -0,0 +1,702 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, inject, NgZone, ChangeDetectorRef, HostListener, ViewChild, ChangeDetectionStrategy, Component, isDevMode, provideAppInitializer, ApplicationRef, EnvironmentInjector, createComponent, NgModule, makeEnvironmentProviders } from '@angular/core';
3
+ import { BehaviorSubject } from 'rxjs';
4
+ import { JsonPipe } from '@angular/common';
5
+ import * as i1 from '@angular/forms';
6
+ import { FormsModule } from '@angular/forms';
7
+
8
+ const DOM_SNAPSHOT_MAX = 5000;
9
+ class InspectorService {
10
+ getComponentContext(element) {
11
+ const component = this.findNearestComponent(element);
12
+ if (!component)
13
+ return null;
14
+ const componentName = component.constructor.name;
15
+ const { component: componentFilePath, template: templateFilePath } = this.resolveFilePaths(componentName);
16
+ const selector = this.getSelector(component);
17
+ const inputs = this.getInputs(component);
18
+ const domSnapshot = this.snapshot(element);
19
+ const componentTreePath = this.buildTreePath(element);
20
+ return {
21
+ componentName,
22
+ componentFilePath,
23
+ ...(templateFilePath ? { templateFilePath } : {}),
24
+ selector,
25
+ inputs,
26
+ domSnapshot,
27
+ componentTreePath,
28
+ };
29
+ }
30
+ findNearestComponent(element) {
31
+ let current = element;
32
+ while (current) {
33
+ try {
34
+ const comp = ng.getComponent(current);
35
+ if (comp)
36
+ return comp;
37
+ }
38
+ catch {
39
+ // ignore
40
+ }
41
+ current = current.parentElement;
42
+ }
43
+ return null;
44
+ }
45
+ getSelector(component) {
46
+ try {
47
+ const cmp = component.constructor.ɵcmp;
48
+ if (!cmp?.selectors?.length)
49
+ return 'unknown-selector';
50
+ const first = cmp.selectors[0];
51
+ if (!first.length)
52
+ return 'unknown-selector';
53
+ // Element selector: [['app-foo']]
54
+ // Attribute selector: [['', 'appFoo', '']]
55
+ if (first[0] === '') {
56
+ // Attribute selector — find non-empty entries
57
+ const attrParts = [];
58
+ for (let i = 1; i < first.length; i += 2) {
59
+ if (typeof first[i] === 'string' && first[i]) {
60
+ attrParts.push(`[${String(first[i])}]`);
61
+ }
62
+ }
63
+ return attrParts.join('') || 'unknown-selector';
64
+ }
65
+ return String(first[0]);
66
+ }
67
+ catch {
68
+ return 'unknown-selector';
69
+ }
70
+ }
71
+ getInputs(component) {
72
+ try {
73
+ const cmp = component.constructor.ɵcmp;
74
+ if (!cmp?.inputs)
75
+ return {};
76
+ const result = {};
77
+ for (const [propName] of Object.entries(cmp.inputs)) {
78
+ if (typeof propName === 'symbol')
79
+ continue;
80
+ if (propName.startsWith('ɵ'))
81
+ continue;
82
+ result[propName] = component[propName];
83
+ }
84
+ return result;
85
+ }
86
+ catch {
87
+ return {};
88
+ }
89
+ }
90
+ buildTreePath(element) {
91
+ const path = [];
92
+ let current = element.parentElement;
93
+ while (current) {
94
+ try {
95
+ const comp = ng.getComponent(current);
96
+ if (comp) {
97
+ path.unshift(comp.constructor.name);
98
+ }
99
+ }
100
+ catch {
101
+ // ignore
102
+ }
103
+ current = current.parentElement;
104
+ }
105
+ return path;
106
+ }
107
+ snapshot(element) {
108
+ const html = element.outerHTML;
109
+ if (html.length <= DOM_SNAPSHOT_MAX)
110
+ return html;
111
+ return html.slice(0, DOM_SNAPSHOT_MAX) + '<!-- truncated -->';
112
+ }
113
+ resolveFilePaths(componentName) {
114
+ try {
115
+ const manifest = window
116
+ .__NG_ANNOTATE_MANIFEST__;
117
+ const entry = manifest?.[componentName];
118
+ if (entry)
119
+ return entry;
120
+ }
121
+ catch {
122
+ // ignore
123
+ }
124
+ return { component: `(unresolved: ${componentName})` };
125
+ }
126
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: InspectorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
127
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: InspectorService });
128
+ }
129
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: InspectorService, decorators: [{
130
+ type: Injectable
131
+ }] });
132
+
133
+ class BridgeService {
134
+ session$ = new BehaviorSubject(null);
135
+ annotations$ = new BehaviorSubject([]);
136
+ connected$ = new BehaviorSubject(false);
137
+ zone = inject(NgZone);
138
+ ws = null;
139
+ reconnectTimer = null;
140
+ init() {
141
+ this.connect();
142
+ }
143
+ connect() {
144
+ const wsUrl = `ws://${location.host}/__annotate`;
145
+ this.ws = new WebSocket(wsUrl);
146
+ this.ws.onopen = () => {
147
+ this.zone.run(() => {
148
+ this.connected$.next(true);
149
+ });
150
+ };
151
+ this.ws.onmessage = (event) => {
152
+ this.zone.run(() => {
153
+ try {
154
+ const data = JSON.parse(event.data);
155
+ if (data.type === 'session:created') {
156
+ this.session$.next(data.session);
157
+ }
158
+ else if (data.type === 'annotations:sync') {
159
+ this.annotations$.next(data.annotations);
160
+ }
161
+ else if (data.type === 'annotation:created') {
162
+ const annotation = data.annotation;
163
+ const current = this.annotations$.getValue();
164
+ this.annotations$.next([...current, annotation]);
165
+ }
166
+ }
167
+ catch {
168
+ // ignore malformed messages
169
+ }
170
+ });
171
+ };
172
+ this.ws.onclose = () => {
173
+ this.zone.run(() => {
174
+ this.connected$.next(false);
175
+ this.reconnectTimer = setTimeout(() => { this.connect(); }, 3000);
176
+ });
177
+ };
178
+ this.ws.onerror = (event) => {
179
+ console.warn('[ng-annotate] WebSocket error', event);
180
+ };
181
+ }
182
+ createAnnotation(payload) {
183
+ this.send({ type: 'annotation:create', payload });
184
+ }
185
+ replyToAnnotation(id, message) {
186
+ this.send({ type: 'annotation:reply', id, message });
187
+ }
188
+ deleteAnnotation(id) {
189
+ this.send({ type: 'annotation:delete', id });
190
+ }
191
+ send(msg) {
192
+ if (this.ws?.readyState === WebSocket.OPEN) {
193
+ this.ws.send(JSON.stringify(msg));
194
+ }
195
+ }
196
+ ngOnDestroy() {
197
+ if (this.reconnectTimer !== null) {
198
+ clearTimeout(this.reconnectTimer);
199
+ this.reconnectTimer = null;
200
+ }
201
+ this.ws?.close();
202
+ }
203
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BridgeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
204
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BridgeService });
205
+ }
206
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BridgeService, decorators: [{
207
+ type: Injectable
208
+ }] });
209
+
210
+ class OverlayComponent {
211
+ textArea;
212
+ mode = 'hidden';
213
+ hoveredContext = null;
214
+ highlightRect = null;
215
+ selectedContext = null;
216
+ annotationText = '';
217
+ selectionText = '';
218
+ threadAnnotation = null;
219
+ replyText = '';
220
+ badges = [];
221
+ inspector = inject(InspectorService);
222
+ bridge = inject(BridgeService);
223
+ cdr = inject(ChangeDetectorRef);
224
+ ngOnInit() {
225
+ this.bridge.annotations$.subscribe((annotations) => {
226
+ this.updateBadges(annotations);
227
+ this.cdr.markForCheck();
228
+ });
229
+ }
230
+ toggleInspect(event) {
231
+ event?.preventDefault();
232
+ if (this.mode === 'hidden')
233
+ this.mode = 'inspect';
234
+ else if (this.mode === 'inspect')
235
+ this.mode = 'hidden';
236
+ else if (this.mode === 'annotate')
237
+ this.mode = 'inspect';
238
+ this.cdr.markForCheck();
239
+ }
240
+ onEscape() {
241
+ if (this.mode === 'annotate')
242
+ this.mode = 'inspect';
243
+ else if (this.mode === 'inspect')
244
+ this.mode = 'hidden';
245
+ else if (this.mode === 'thread')
246
+ this.mode = 'hidden';
247
+ this.cdr.markForCheck();
248
+ }
249
+ onScrollOrResize() {
250
+ if (this.badges.length > 0) {
251
+ this.refreshBadgePositions();
252
+ this.cdr.markForCheck();
253
+ }
254
+ }
255
+ onMouseMove(event) {
256
+ if (this.mode !== 'inspect')
257
+ return;
258
+ const target = event.target;
259
+ if (target.closest('nga-overlay'))
260
+ return;
261
+ const context = this.inspector.getComponentContext(target);
262
+ this.hoveredContext = context;
263
+ if (context) {
264
+ const rect = target.getBoundingClientRect();
265
+ this.highlightRect = {
266
+ top: `${rect.top.toString()}px`,
267
+ left: `${rect.left.toString()}px`,
268
+ width: `${rect.width.toString()}px`,
269
+ height: `${rect.height.toString()}px`,
270
+ };
271
+ }
272
+ else {
273
+ this.highlightRect = null;
274
+ }
275
+ this.cdr.markForCheck();
276
+ }
277
+ onClick(event) {
278
+ if (this.mode !== 'inspect')
279
+ return;
280
+ const target = event.target;
281
+ if (target.closest('nga-overlay'))
282
+ return;
283
+ const context = this.inspector.getComponentContext(target);
284
+ if (!context)
285
+ return;
286
+ event.preventDefault();
287
+ event.stopPropagation();
288
+ this.selectedContext = context;
289
+ this.annotationText = '';
290
+ this.selectionText = window.getSelection()?.toString() ?? '';
291
+ this.mode = 'annotate';
292
+ this.cdr.markForCheck();
293
+ setTimeout(() => { this.textArea?.nativeElement.focus(); }, 0);
294
+ }
295
+ submit() {
296
+ if (!this.selectedContext || !this.annotationText.trim())
297
+ return;
298
+ this.bridge.createAnnotation({
299
+ ...this.selectedContext,
300
+ annotationText: this.annotationText.trim(),
301
+ selectionText: this.selectionText || undefined,
302
+ });
303
+ this.selectedContext = null;
304
+ this.annotationText = '';
305
+ this.mode = 'inspect';
306
+ this.cdr.markForCheck();
307
+ }
308
+ cancel() {
309
+ this.mode = 'inspect';
310
+ this.cdr.markForCheck();
311
+ }
312
+ openThread(annotation) {
313
+ this.threadAnnotation = annotation;
314
+ this.mode = 'thread';
315
+ this.cdr.markForCheck();
316
+ }
317
+ closeThread() {
318
+ this.threadAnnotation = null;
319
+ this.mode = 'hidden';
320
+ this.cdr.markForCheck();
321
+ }
322
+ sendReply() {
323
+ if (!this.threadAnnotation || !this.replyText.trim())
324
+ return;
325
+ this.bridge.replyToAnnotation(this.threadAnnotation.id, this.replyText.trim());
326
+ this.replyText = '';
327
+ this.cdr.markForCheck();
328
+ }
329
+ inputEntries() {
330
+ if (!this.selectedContext)
331
+ return [];
332
+ return Object.entries(this.selectedContext.inputs)
333
+ .slice(0, 5)
334
+ .map(([key, value]) => ({ key, value }));
335
+ }
336
+ updateBadges(annotations) {
337
+ this.badges = annotations
338
+ .map((annotation) => {
339
+ const el = this.findComponentElement(annotation.componentName, annotation.selector);
340
+ if (!el)
341
+ return null;
342
+ const rect = el.getBoundingClientRect();
343
+ return {
344
+ annotation,
345
+ top: `${rect.top.toString()}px`,
346
+ left: `${(rect.left + rect.width - 12).toString()}px`,
347
+ icon: this.badgeIcon(annotation.status),
348
+ label: `${annotation.componentName}: ${annotation.annotationText.slice(0, 40)}`,
349
+ };
350
+ })
351
+ .filter((b) => b !== null);
352
+ }
353
+ refreshBadgePositions() {
354
+ this.badges = this.badges.map((badge) => {
355
+ const el = this.findComponentElement(badge.annotation.componentName, badge.annotation.selector);
356
+ if (!el)
357
+ return badge;
358
+ const rect = el.getBoundingClientRect();
359
+ return {
360
+ ...badge,
361
+ top: `${rect.top.toString()}px`,
362
+ left: `${(rect.left + rect.width - 12).toString()}px`,
363
+ };
364
+ });
365
+ }
366
+ findComponentElement(componentName, selector) {
367
+ const bySelector = document.querySelector(selector);
368
+ if (bySelector)
369
+ return bySelector;
370
+ const all = document.querySelectorAll('*');
371
+ for (const el of Array.from(all)) {
372
+ try {
373
+ const comp = window
374
+ .ng;
375
+ if (comp?.getComponent(el)?.constructor.name === componentName)
376
+ return el;
377
+ }
378
+ catch {
379
+ // ignore
380
+ }
381
+ }
382
+ return null;
383
+ }
384
+ badgeIcon(status) {
385
+ const icons = {
386
+ pending: '●',
387
+ acknowledged: '◐',
388
+ resolved: '✓',
389
+ dismissed: '✕',
390
+ };
391
+ return icons[status];
392
+ }
393
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
394
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: OverlayComponent, isStandalone: true, selector: "nga-overlay", host: { listeners: { "document:keydown.alt.shift.a": "toggleInspect($event)", "document:keydown.escape": "onEscape()", "window:scroll": "onScrollOrResize()", "window:resize": "onScrollOrResize()", "document:mousemove": "onMouseMove($event)", "document:click": "onClick($event)" } }, viewQueries: [{ propertyName: "textArea", first: true, predicate: ["textArea"], descendants: true }], ngImport: i0, template: `
395
+ <!-- Keyboard hint -->
396
+ @if (mode === 'hidden') {
397
+ <div class="nga-keyboard-hint">
398
+ <kbd>Alt+Shift+A</kbd> to annotate
399
+ </div>
400
+ }
401
+ @if (mode === 'inspect') {
402
+ <div class="nga-keyboard-hint">
403
+ Click a component &nbsp; <kbd>Esc</kbd> to cancel
404
+ </div>
405
+ }
406
+
407
+ <!-- Inspect highlight rect -->
408
+ @if (mode === 'inspect' && hoveredContext !== null && highlightRect !== null) {
409
+ <div
410
+ class="nga-highlight-rect"
411
+ [style.top]="highlightRect.top"
412
+ [style.left]="highlightRect.left"
413
+ [style.width]="highlightRect.width"
414
+ [style.height]="highlightRect.height"
415
+ >
416
+ <span class="nga-component-label">{{ hoveredContext.componentName }}</span>
417
+ </div>
418
+ }
419
+
420
+ <!-- Annotate panel -->
421
+ @if (mode === 'annotate' && selectedContext !== null) {
422
+ <div class="nga-annotate-panel">
423
+ <h3 class="nga-panel-title">{{ selectedContext.componentName }}</h3>
424
+
425
+ @if (inputEntries().length > 0) {
426
+ <div class="nga-inputs">
427
+ @for (entry of inputEntries(); track entry.key) {
428
+ <div class="nga-input-row">
429
+ <span class="nga-input-key">{{ entry.key }}:</span>
430
+ <span class="nga-input-val">{{ entry.value | json }}</span>
431
+ </div>
432
+ }
433
+ </div>
434
+ }
435
+
436
+ @if (selectionText) {
437
+ <div class="nga-selection">
438
+ <em>"{{ selectionText }}"</em>
439
+ </div>
440
+ }
441
+
442
+ <textarea
443
+ #textArea
444
+ class="nga-textarea"
445
+ [(ngModel)]="annotationText"
446
+ placeholder="Describe the change..."
447
+ rows="4"
448
+ ></textarea>
449
+
450
+ <div class="nga-actions">
451
+ <button class="nga-btn nga-btn-submit" (click)="submit()" [disabled]="annotationText.trim() === ''">
452
+ Submit
453
+ </button>
454
+ <button class="nga-btn nga-btn-cancel" (click)="cancel()">Cancel</button>
455
+ </div>
456
+ </div>
457
+ }
458
+
459
+ <!-- Thread panel -->
460
+ @if (mode === 'thread' && threadAnnotation !== null) {
461
+ <div class="nga-thread-panel">
462
+ <h3 class="nga-panel-title">{{ threadAnnotation.componentName }}</h3>
463
+
464
+ <div class="nga-replies">
465
+ @for (reply of threadAnnotation.replies; track reply.message) {
466
+ <div class="nga-reply">
467
+ <span class="nga-reply-author nga-reply-author--{{ reply.author }}">{{ reply.author }}</span>
468
+ <span class="nga-reply-text">{{ reply.message }}</span>
469
+ </div>
470
+ }
471
+ </div>
472
+
473
+ <input
474
+ class="nga-reply-input"
475
+ type="text"
476
+ [(ngModel)]="replyText"
477
+ placeholder="Reply..."
478
+ (keydown.enter)="sendReply()"
479
+ />
480
+
481
+ <div class="nga-actions">
482
+ <button class="nga-btn nga-btn-submit" (click)="sendReply()" [disabled]="replyText.trim() === ''">
483
+ Send
484
+ </button>
485
+ <button class="nga-btn nga-btn-cancel" (click)="closeThread()">Close</button>
486
+ </div>
487
+ </div>
488
+ }
489
+
490
+ <!-- Annotation badges -->
491
+ @for (badge of badges; track badge.annotation.id) {
492
+ <div
493
+ class="nga-badge nga-badge--{{ badge.annotation.status }}"
494
+ [style.top]="badge.top"
495
+ [style.left]="badge.left"
496
+ (click)="openThread(badge.annotation)"
497
+ [title]="badge.label"
498
+ >
499
+ {{ badge.icon }}
500
+ </div>
501
+ }
502
+ `, isInline: true, styles: [":host{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999}.nga-highlight-rect{position:fixed;border:2px solid #3b82f6;background:#3b82f61a;pointer-events:none;transition:top .05s,left .05s,width .05s,height .05s}.nga-component-label{position:absolute;top:-22px;left:0;background:#1e293b;color:#f8fafc;font-family:monospace;font-size:11px;padding:2px 6px;border-radius:3px;white-space:nowrap}.nga-annotate-panel,.nga-thread-panel{pointer-events:all;position:fixed;right:16px;top:50%;transform:translateY(-50%);background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 24px #00000026;padding:16px;min-width:320px;max-width:400px}.nga-panel-title{margin:0 0 12px;font-size:14px;font-weight:600;color:#1e293b;font-family:monospace}.nga-inputs{margin-bottom:10px}.nga-input-row{display:flex;gap:8px;font-size:12px;font-family:monospace;margin-bottom:4px}.nga-input-key{color:#64748b;min-width:80px}.nga-input-val{color:#1e293b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nga-selection{font-size:12px;color:#475569;margin-bottom:8px;font-style:italic}.nga-textarea,.nga-reply-input{width:100%;box-sizing:border-box;border:1px solid #cbd5e1;border-radius:4px;padding:8px;font-size:13px;font-family:inherit;resize:vertical;margin-bottom:10px}.nga-textarea:focus,.nga-reply-input:focus{outline:none;border-color:#3b82f6}.nga-actions{display:flex;gap:8px}.nga-btn{padding:6px 14px;border-radius:4px;font-size:13px;cursor:pointer;border:none;transition:opacity .15s}.nga-btn:disabled{opacity:.4;cursor:not-allowed}.nga-btn-submit{background:#3b82f6;color:#fff}.nga-btn-cancel{background:#f1f5f9;color:#475569}.nga-replies{max-height:200px;overflow-y:auto;margin-bottom:10px}.nga-reply{display:flex;gap:8px;margin-bottom:8px;font-size:13px}.nga-reply-author{font-weight:600;min-width:48px}.nga-reply-author--agent{color:#7c3aed}.nga-reply-author--user{color:#2563eb}.nga-badge{pointer-events:all;position:fixed;width:18px;height:18px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;cursor:pointer;transform:translate(-50%,-50%)}.nga-badge--pending{background:#3b82f6;color:#fff}.nga-badge--acknowledged{background:#f59e0b;color:#fff}.nga-badge--resolved{background:#22c55e;color:#fff}.nga-badge--dismissed{background:#94a3b8;color:#fff}.nga-keyboard-hint{pointer-events:none;position:fixed;bottom:16px;right:16px;background:#0f172ab3;color:#f8fafc;font-size:12px;padding:6px 10px;border-radius:6px}.nga-keyboard-hint kbd{background:#ffffff26;border-radius:3px;padding:1px 5px;font-family:monospace}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
503
+ }
504
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OverlayComponent, decorators: [{
505
+ type: Component,
506
+ args: [{ selector: 'nga-overlay', changeDetection: ChangeDetectionStrategy.OnPush, imports: [JsonPipe, FormsModule], template: `
507
+ <!-- Keyboard hint -->
508
+ @if (mode === 'hidden') {
509
+ <div class="nga-keyboard-hint">
510
+ <kbd>Alt+Shift+A</kbd> to annotate
511
+ </div>
512
+ }
513
+ @if (mode === 'inspect') {
514
+ <div class="nga-keyboard-hint">
515
+ Click a component &nbsp; <kbd>Esc</kbd> to cancel
516
+ </div>
517
+ }
518
+
519
+ <!-- Inspect highlight rect -->
520
+ @if (mode === 'inspect' && hoveredContext !== null && highlightRect !== null) {
521
+ <div
522
+ class="nga-highlight-rect"
523
+ [style.top]="highlightRect.top"
524
+ [style.left]="highlightRect.left"
525
+ [style.width]="highlightRect.width"
526
+ [style.height]="highlightRect.height"
527
+ >
528
+ <span class="nga-component-label">{{ hoveredContext.componentName }}</span>
529
+ </div>
530
+ }
531
+
532
+ <!-- Annotate panel -->
533
+ @if (mode === 'annotate' && selectedContext !== null) {
534
+ <div class="nga-annotate-panel">
535
+ <h3 class="nga-panel-title">{{ selectedContext.componentName }}</h3>
536
+
537
+ @if (inputEntries().length > 0) {
538
+ <div class="nga-inputs">
539
+ @for (entry of inputEntries(); track entry.key) {
540
+ <div class="nga-input-row">
541
+ <span class="nga-input-key">{{ entry.key }}:</span>
542
+ <span class="nga-input-val">{{ entry.value | json }}</span>
543
+ </div>
544
+ }
545
+ </div>
546
+ }
547
+
548
+ @if (selectionText) {
549
+ <div class="nga-selection">
550
+ <em>"{{ selectionText }}"</em>
551
+ </div>
552
+ }
553
+
554
+ <textarea
555
+ #textArea
556
+ class="nga-textarea"
557
+ [(ngModel)]="annotationText"
558
+ placeholder="Describe the change..."
559
+ rows="4"
560
+ ></textarea>
561
+
562
+ <div class="nga-actions">
563
+ <button class="nga-btn nga-btn-submit" (click)="submit()" [disabled]="annotationText.trim() === ''">
564
+ Submit
565
+ </button>
566
+ <button class="nga-btn nga-btn-cancel" (click)="cancel()">Cancel</button>
567
+ </div>
568
+ </div>
569
+ }
570
+
571
+ <!-- Thread panel -->
572
+ @if (mode === 'thread' && threadAnnotation !== null) {
573
+ <div class="nga-thread-panel">
574
+ <h3 class="nga-panel-title">{{ threadAnnotation.componentName }}</h3>
575
+
576
+ <div class="nga-replies">
577
+ @for (reply of threadAnnotation.replies; track reply.message) {
578
+ <div class="nga-reply">
579
+ <span class="nga-reply-author nga-reply-author--{{ reply.author }}">{{ reply.author }}</span>
580
+ <span class="nga-reply-text">{{ reply.message }}</span>
581
+ </div>
582
+ }
583
+ </div>
584
+
585
+ <input
586
+ class="nga-reply-input"
587
+ type="text"
588
+ [(ngModel)]="replyText"
589
+ placeholder="Reply..."
590
+ (keydown.enter)="sendReply()"
591
+ />
592
+
593
+ <div class="nga-actions">
594
+ <button class="nga-btn nga-btn-submit" (click)="sendReply()" [disabled]="replyText.trim() === ''">
595
+ Send
596
+ </button>
597
+ <button class="nga-btn nga-btn-cancel" (click)="closeThread()">Close</button>
598
+ </div>
599
+ </div>
600
+ }
601
+
602
+ <!-- Annotation badges -->
603
+ @for (badge of badges; track badge.annotation.id) {
604
+ <div
605
+ class="nga-badge nga-badge--{{ badge.annotation.status }}"
606
+ [style.top]="badge.top"
607
+ [style.left]="badge.left"
608
+ (click)="openThread(badge.annotation)"
609
+ [title]="badge.label"
610
+ >
611
+ {{ badge.icon }}
612
+ </div>
613
+ }
614
+ `, styles: [":host{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999}.nga-highlight-rect{position:fixed;border:2px solid #3b82f6;background:#3b82f61a;pointer-events:none;transition:top .05s,left .05s,width .05s,height .05s}.nga-component-label{position:absolute;top:-22px;left:0;background:#1e293b;color:#f8fafc;font-family:monospace;font-size:11px;padding:2px 6px;border-radius:3px;white-space:nowrap}.nga-annotate-panel,.nga-thread-panel{pointer-events:all;position:fixed;right:16px;top:50%;transform:translateY(-50%);background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 4px 24px #00000026;padding:16px;min-width:320px;max-width:400px}.nga-panel-title{margin:0 0 12px;font-size:14px;font-weight:600;color:#1e293b;font-family:monospace}.nga-inputs{margin-bottom:10px}.nga-input-row{display:flex;gap:8px;font-size:12px;font-family:monospace;margin-bottom:4px}.nga-input-key{color:#64748b;min-width:80px}.nga-input-val{color:#1e293b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nga-selection{font-size:12px;color:#475569;margin-bottom:8px;font-style:italic}.nga-textarea,.nga-reply-input{width:100%;box-sizing:border-box;border:1px solid #cbd5e1;border-radius:4px;padding:8px;font-size:13px;font-family:inherit;resize:vertical;margin-bottom:10px}.nga-textarea:focus,.nga-reply-input:focus{outline:none;border-color:#3b82f6}.nga-actions{display:flex;gap:8px}.nga-btn{padding:6px 14px;border-radius:4px;font-size:13px;cursor:pointer;border:none;transition:opacity .15s}.nga-btn:disabled{opacity:.4;cursor:not-allowed}.nga-btn-submit{background:#3b82f6;color:#fff}.nga-btn-cancel{background:#f1f5f9;color:#475569}.nga-replies{max-height:200px;overflow-y:auto;margin-bottom:10px}.nga-reply{display:flex;gap:8px;margin-bottom:8px;font-size:13px}.nga-reply-author{font-weight:600;min-width:48px}.nga-reply-author--agent{color:#7c3aed}.nga-reply-author--user{color:#2563eb}.nga-badge{pointer-events:all;position:fixed;width:18px;height:18px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;cursor:pointer;transform:translate(-50%,-50%)}.nga-badge--pending{background:#3b82f6;color:#fff}.nga-badge--acknowledged{background:#f59e0b;color:#fff}.nga-badge--resolved{background:#22c55e;color:#fff}.nga-badge--dismissed{background:#94a3b8;color:#fff}.nga-keyboard-hint{pointer-events:none;position:fixed;bottom:16px;right:16px;background:#0f172ab3;color:#f8fafc;font-size:12px;padding:6px 10px;border-radius:6px}.nga-keyboard-hint kbd{background:#ffffff26;border-radius:3px;padding:1px 5px;font-family:monospace}\n"] }]
615
+ }], propDecorators: { textArea: [{
616
+ type: ViewChild,
617
+ args: ['textArea']
618
+ }], toggleInspect: [{
619
+ type: HostListener,
620
+ args: ['document:keydown.alt.shift.a', ['$event']]
621
+ }], onEscape: [{
622
+ type: HostListener,
623
+ args: ['document:keydown.escape']
624
+ }], onScrollOrResize: [{
625
+ type: HostListener,
626
+ args: ['window:scroll']
627
+ }, {
628
+ type: HostListener,
629
+ args: ['window:resize']
630
+ }], onMouseMove: [{
631
+ type: HostListener,
632
+ args: ['document:mousemove', ['$event']]
633
+ }], onClick: [{
634
+ type: HostListener,
635
+ args: ['document:click', ['$event']]
636
+ }] } });
637
+
638
+ // eslint-disable-next-line @typescript-eslint/no-extraneous-class -- required by NgModule pattern
639
+ class NgAnnotateModule {
640
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgAnnotateModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
641
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.0", ngImport: i0, type: NgAnnotateModule });
642
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgAnnotateModule, providers: isDevMode()
643
+ ? [
644
+ InspectorService,
645
+ BridgeService,
646
+ provideAppInitializer(() => {
647
+ const bridge = inject(BridgeService);
648
+ const appRef = inject(ApplicationRef);
649
+ const envInjector = inject(EnvironmentInjector);
650
+ bridge.init();
651
+ const overlayRef = createComponent(OverlayComponent, { environmentInjector: envInjector });
652
+ appRef.attachView(overlayRef.hostView);
653
+ document.body.appendChild(overlayRef.location.nativeElement);
654
+ }),
655
+ ]
656
+ : [] });
657
+ }
658
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: NgAnnotateModule, decorators: [{
659
+ type: NgModule,
660
+ args: [{
661
+ providers: isDevMode()
662
+ ? [
663
+ InspectorService,
664
+ BridgeService,
665
+ provideAppInitializer(() => {
666
+ const bridge = inject(BridgeService);
667
+ const appRef = inject(ApplicationRef);
668
+ const envInjector = inject(EnvironmentInjector);
669
+ bridge.init();
670
+ const overlayRef = createComponent(OverlayComponent, { environmentInjector: envInjector });
671
+ appRef.attachView(overlayRef.hostView);
672
+ document.body.appendChild(overlayRef.location.nativeElement);
673
+ }),
674
+ ]
675
+ : [],
676
+ }]
677
+ }] });
678
+
679
+ function provideNgAnnotate() {
680
+ return makeEnvironmentProviders([
681
+ InspectorService,
682
+ BridgeService,
683
+ provideAppInitializer(() => {
684
+ if (!isDevMode())
685
+ return;
686
+ const bridge = inject(BridgeService);
687
+ const appRef = inject(ApplicationRef);
688
+ const envInjector = inject(EnvironmentInjector);
689
+ bridge.init();
690
+ const overlayRef = createComponent(OverlayComponent, { environmentInjector: envInjector });
691
+ appRef.attachView(overlayRef.hostView);
692
+ document.body.appendChild(overlayRef.location.nativeElement);
693
+ }),
694
+ ]);
695
+ }
696
+
697
+ /**
698
+ * Generated bundle index. Do not edit.
699
+ */
700
+
701
+ export { BridgeService, InspectorService, NgAnnotateModule, OverlayComponent, provideNgAnnotate };
702
+ //# sourceMappingURL=ng-annotate-angular.mjs.map