inspector-ng 0.0.5 → 0.0.7

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.
@@ -1,12 +1,276 @@
1
1
  import * as i0 from '@angular/core';
2
- import { viewChild, signal, computed, effect, PLATFORM_ID, HostListener, Input, Inject, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { signal, computed, PLATFORM_ID, Inject, Injectable, viewChild, inject, effect, HostListener, Input, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { isPlatformBrowser } from '@angular/common';
4
+ import * as i1 from '@angular/router';
5
+ import { Router } from '@angular/router';
4
6
 
5
7
  const inspector_STORAGE_KEY = "inspector-state";
6
- const inspector_STATE_VERSION = 1;
8
+ const inspector_STATE_VERSION = 2;
7
9
  const GUIDE_SNAP_DISTANCE = 10;
8
10
  const GUIDE_HITBOX_SIZE = 14;
9
11
 
12
+ const STORAGE_KEY = 'inspector-checkpoints-v1';
13
+ class InspectorCheckpointRegistry {
14
+ constructor(platformId, router) {
15
+ this.router = router;
16
+ this.checkpoints = signal([]);
17
+ this.isBusy = signal(false);
18
+ this.error = signal(null);
19
+ this.warning = signal(null);
20
+ this.totalBytes = computed(() => this.checkpoints().reduce((total, checkpoint) => total + checkpoint.sizeBytes, 0));
21
+ this.adapters = new Map();
22
+ this.remoteLoaders = new Map();
23
+ this.isCheckpoint = (value) => {
24
+ if (!value || typeof value !== 'object')
25
+ return false;
26
+ const checkpoint = value;
27
+ return checkpoint.version === 1 && typeof checkpoint.id === 'string' &&
28
+ typeof checkpoint.name === 'string' && typeof checkpoint.url === 'string' &&
29
+ typeof checkpoint.createdAt === 'string' && Array.isArray(checkpoint.scopes) &&
30
+ !!checkpoint.state && typeof checkpoint.state === 'object' && typeof checkpoint.sizeBytes === 'number';
31
+ };
32
+ this.isBrowser = isPlatformBrowser(platformId);
33
+ this.hydrate();
34
+ }
35
+ register(adapter) {
36
+ if (!adapter.id.includes(':')) {
37
+ throw new Error('Checkpoint adapter IDs must be namespaced (for example, workflow:application).');
38
+ }
39
+ if (this.adapters.has(adapter.id)) {
40
+ // Remote bridge loaders can run more than once across checkpoint restores.
41
+ // Treat repeated registration as idempotent so a loaded remote stays usable.
42
+ return () => undefined;
43
+ }
44
+ this.adapters.set(adapter.id, adapter);
45
+ return () => this.adapters.delete(adapter.id);
46
+ }
47
+ registerRemoteScope(scope, loader) {
48
+ this.remoteLoaders.set(scope, loader);
49
+ return () => this.remoteLoaders.delete(scope);
50
+ }
51
+ async save(name) {
52
+ this.error.set(null);
53
+ if (!this.isBrowser) {
54
+ this.error.set('Checkpoints are only available in a browser.');
55
+ return null;
56
+ }
57
+ this.isBusy.set(true);
58
+ try {
59
+ const state = {};
60
+ for (const adapter of this.adapters.values()) {
61
+ state[adapter.id] = this.cloneValue(await adapter.capture(), adapter.id);
62
+ }
63
+ const createdAt = new Date().toISOString();
64
+ const checkpoint = {
65
+ version: 1,
66
+ id: this.createId(),
67
+ name: name?.trim() || this.nextAutomaticName(this.routePath(this.router.url)),
68
+ url: this.router.url,
69
+ createdAt,
70
+ scopes: this.getScopes(Object.keys(state)),
71
+ state,
72
+ sizeBytes: 0,
73
+ };
74
+ checkpoint.sizeBytes = this.sizeOf(checkpoint);
75
+ this.persistWithEviction([checkpoint, ...this.checkpoints()]);
76
+ return this.checkpoints().find((item) => item.id === checkpoint.id) ?? null;
77
+ }
78
+ catch (error) {
79
+ this.error.set(this.messageFor(error, 'Unable to save checkpoint.'));
80
+ return null;
81
+ }
82
+ finally {
83
+ this.isBusy.set(false);
84
+ }
85
+ }
86
+ async restore(id) {
87
+ const checkpoint = this.checkpoints().find((item) => item.id === id);
88
+ this.error.set(null);
89
+ this.warning.set(null);
90
+ if (!checkpoint) {
91
+ this.error.set('Checkpoint not found.');
92
+ return false;
93
+ }
94
+ this.isBusy.set(true);
95
+ try {
96
+ for (const scope of checkpoint.scopes) {
97
+ const loader = this.remoteLoaders.get(scope);
98
+ if (loader) {
99
+ await loader();
100
+ }
101
+ }
102
+ const missing = [];
103
+ for (const [adapterId, value] of Object.entries(checkpoint.state)) {
104
+ const adapter = this.adapters.get(adapterId);
105
+ if (!adapter) {
106
+ missing.push(adapterId);
107
+ continue;
108
+ }
109
+ await adapter.restore(this.cloneValue(value, adapterId));
110
+ }
111
+ if (missing.length) {
112
+ this.warning.set(`Skipped ${missing.length} unavailable adapter${missing.length === 1 ? '' : 's'}: ${missing.join(', ')}.`);
113
+ }
114
+ await this.router.navigateByUrl(checkpoint.url);
115
+ return true;
116
+ }
117
+ catch (error) {
118
+ this.error.set(this.messageFor(error, 'Unable to restore checkpoint.'));
119
+ return false;
120
+ }
121
+ finally {
122
+ this.isBusy.set(false);
123
+ }
124
+ }
125
+ rename(id, name) {
126
+ const nextName = name.trim();
127
+ if (!nextName) {
128
+ this.error.set('A checkpoint name is required.');
129
+ return;
130
+ }
131
+ this.error.set(null);
132
+ const next = this.checkpoints().map((checkpoint) => checkpoint.id === id ? { ...checkpoint, name: nextName } : checkpoint);
133
+ this.persistWithEviction(next);
134
+ }
135
+ delete(id) {
136
+ this.error.set(null);
137
+ this.persistWithEviction(this.checkpoints().filter((checkpoint) => checkpoint.id !== id));
138
+ }
139
+ hydrate() {
140
+ if (!this.isBrowser) {
141
+ return;
142
+ }
143
+ try {
144
+ const raw = window.localStorage.getItem(STORAGE_KEY);
145
+ if (!raw)
146
+ return;
147
+ const parsed = JSON.parse(raw);
148
+ if (!Array.isArray(parsed))
149
+ return;
150
+ const checkpoints = parsed.filter(this.isCheckpoint);
151
+ const normalized = this.normalizeLegacyNames(checkpoints);
152
+ const sorted = normalized.checkpoints.sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id));
153
+ this.checkpoints.set(sorted);
154
+ if (normalized.changed) {
155
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(sorted));
156
+ }
157
+ }
158
+ catch {
159
+ this.warning.set('Saved checkpoints could not be read and were ignored.');
160
+ }
161
+ }
162
+ persistWithEviction(checkpoints) {
163
+ if (!this.isBrowser)
164
+ return;
165
+ let candidate = checkpoints;
166
+ while (true) {
167
+ try {
168
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(candidate));
169
+ this.checkpoints.set(candidate);
170
+ return;
171
+ }
172
+ catch (error) {
173
+ if (!this.isQuotaError(error) || candidate.length === 0) {
174
+ throw error;
175
+ }
176
+ candidate = candidate.slice(0, -1);
177
+ this.warning.set('Storage was full; the oldest checkpoint was removed.');
178
+ }
179
+ }
180
+ }
181
+ cloneValue(value, adapterId) {
182
+ try {
183
+ const serialized = JSON.stringify(value);
184
+ if (serialized === undefined)
185
+ throw new Error('undefined values are not supported');
186
+ return JSON.parse(serialized);
187
+ }
188
+ catch {
189
+ throw new Error(`Adapter "${adapterId}" returned a value that cannot be JSON serialized.`);
190
+ }
191
+ }
192
+ getScopes(adapterIds) {
193
+ return [...new Set(adapterIds.map((id) => id.split(':', 1)[0]))];
194
+ }
195
+ routePath(url) {
196
+ return url.split(/[?#]/, 1)[0] || '/';
197
+ }
198
+ nextAutomaticName(route, usedNames = new Set(this.checkpoints().map(({ name }) => name))) {
199
+ if (!usedNames.has(route))
200
+ return route;
201
+ for (let suffix = 2;; suffix += 1) {
202
+ const candidate = `${route} ${suffix}`;
203
+ if (!usedNames.has(candidate))
204
+ return candidate;
205
+ }
206
+ }
207
+ normalizeLegacyNames(checkpoints) {
208
+ const legacyIds = new Set(checkpoints.filter((checkpoint) => this.isLegacyAutomaticName(checkpoint)).map(({ id }) => id));
209
+ if (!legacyIds.size)
210
+ return { checkpoints, changed: false };
211
+ const usedNames = new Set(checkpoints.filter(({ id }) => !legacyIds.has(id)).map(({ name }) => name));
212
+ const names = new Map();
213
+ const oldestFirst = checkpoints
214
+ .filter(({ id }) => legacyIds.has(id))
215
+ .sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
216
+ for (const checkpoint of oldestFirst) {
217
+ const name = this.nextAutomaticName(this.routePath(checkpoint.url), usedNames);
218
+ names.set(checkpoint.id, name);
219
+ usedNames.add(name);
220
+ }
221
+ return {
222
+ checkpoints: checkpoints.map((checkpoint) => names.has(checkpoint.id) ? { ...checkpoint, name: names.get(checkpoint.id) } : checkpoint),
223
+ changed: true,
224
+ };
225
+ }
226
+ isLegacyAutomaticName(checkpoint) {
227
+ const route = this.routePath(checkpoint.url);
228
+ const prefix = `${route} · `;
229
+ if (!checkpoint.name.startsWith(prefix))
230
+ return false;
231
+ const timestamp = checkpoint.name.slice(prefix.length);
232
+ if (timestamp === new Date(checkpoint.createdAt).toLocaleString())
233
+ return true;
234
+ const parsedTimestamp = Date.parse(timestamp);
235
+ const createdTimestamp = Date.parse(checkpoint.createdAt);
236
+ return Number.isFinite(parsedTimestamp) && Number.isFinite(createdTimestamp) &&
237
+ Math.abs(parsedTimestamp - createdTimestamp) < 1000;
238
+ }
239
+ createId() {
240
+ return `checkpoint-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
241
+ }
242
+ sizeOf(checkpoint) {
243
+ return new Blob([JSON.stringify(checkpoint)]).size;
244
+ }
245
+ isQuotaError(error) {
246
+ return error instanceof DOMException && (error.name === 'QuotaExceededError' || error.name === 'NS_ERROR_DOM_QUOTA_REACHED');
247
+ }
248
+ messageFor(error, fallback) {
249
+ return error instanceof Error ? error.message : fallback;
250
+ }
251
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: InspectorCheckpointRegistry, deps: [{ token: PLATFORM_ID }, { token: Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
252
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: InspectorCheckpointRegistry }); }
253
+ }
254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: InspectorCheckpointRegistry, decorators: [{
255
+ type: Injectable
256
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
257
+ type: Inject,
258
+ args: [PLATFORM_ID]
259
+ }] }, { type: i1.Router, decorators: [{
260
+ type: Inject,
261
+ args: [Router]
262
+ }] }] });
263
+ function provideInspectorCheckpoints() {
264
+ return [InspectorCheckpointRegistry];
265
+ }
266
+ function createBehaviorSubjectCheckpointAdapter(id, subject) {
267
+ return {
268
+ id,
269
+ capture: () => subject.value,
270
+ restore: (value) => subject.next(value),
271
+ };
272
+ }
273
+
10
274
  const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
11
275
  const getViewportSize = () => ({
12
276
  width: typeof window === 'undefined' ? 1 : window.innerWidth || 1,
@@ -277,34 +541,46 @@ class inspectorComponent {
277
541
  this.Math = Math;
278
542
  this.formatValue = formatValue;
279
543
  this.guideHitboxSize = GUIDE_HITBOX_SIZE;
280
- this.overlayRoot = viewChild("overlayRoot");
281
- this.highlightColor = "#4f8cff";
282
- this.guideColor = "#ff7a00";
544
+ this.overlayRoot = viewChild('overlayRoot');
545
+ this.highlightColor = '#4f8cff';
546
+ this.guideColor = '#ff7a00';
283
547
  this.hoverHighlightEnabled = true;
284
548
  this.persistOnReload = false;
285
- this.enabled = signal(false);
286
- this.toolMode = signal("none");
287
- this.guideOrientation = signal("vertical");
549
+ this.checkpointRegistry = inject(InspectorCheckpointRegistry, { optional: true });
550
+ this.checkpointsOpen = signal(false);
551
+ this.editingCheckpointId = signal(null);
552
+ this.enabled = signal(true);
553
+ this.toolMode = signal('none');
554
+ this.guideOrientation = signal('vertical');
288
555
  this.guides = signal([]);
289
556
  this.hoverRect = signal(null);
290
557
  this.selectedGuideId = signal(null);
291
558
  this.selectedMeasurement = signal(null);
292
- this.showTypography = signal(true);
559
+ this.showTypography = signal(false);
293
560
  this.altPressed = signal(false);
294
561
  this.distanceOverlay = signal(null);
295
562
  this.textBlocks = signal([]);
296
- this.guideMenuOpen = signal(false);
297
563
  this.guidePreview = signal(null);
298
564
  this.selectedMetaLine = computed(() => {
299
565
  const selected = this.selectedMeasurement();
300
566
  if (!selected) {
301
- return "";
567
+ return '';
302
568
  }
303
569
  return `${selected.label} · ${formatValue(selected.rect.width)} x ${formatValue(selected.rect.height)} · ${selected.styles.fontSize} / ${selected.styles.lineHeight} / ${selected.styles.color}`;
304
570
  });
305
571
  this.hydrated = signal(false);
306
572
  this.history = signal([]);
307
573
  this.historyIndex = signal(-1);
574
+ this.handleCapturedClick = (event) => {
575
+ if (!event.shiftKey ||
576
+ !this.canInspectClick(event) ||
577
+ !this.selectElementAtPoint(event)) {
578
+ return;
579
+ }
580
+ event.preventDefault();
581
+ event.stopPropagation();
582
+ event.stopImmediatePropagation();
583
+ };
308
584
  this.draggingGuideId = null;
309
585
  this.selectedElement = null;
310
586
  this.hoverElement = null;
@@ -331,7 +607,14 @@ class inspectorComponent {
331
607
  });
332
608
  }
333
609
  ngOnInit() {
334
- if (!this.isBrowser || !this.persistOnReload) {
610
+ if (!this.isBrowser) {
611
+ this.hydrated.set(true);
612
+ return;
613
+ }
614
+ window.addEventListener('click', this.handleCapturedClick, {
615
+ capture: true,
616
+ });
617
+ if (!this.persistOnReload) {
335
618
  this.hydrated.set(true);
336
619
  return;
337
620
  }
@@ -342,12 +625,12 @@ class inspectorComponent {
342
625
  }
343
626
  try {
344
627
  const parsed = JSON.parse(stored);
345
- if (parsed.version === inspector_STATE_VERSION) {
346
- this.enabled.set(parsed.enabled);
628
+ if (parsed.version === inspector_STATE_VERSION || parsed.version === 1) {
629
+ this.enabled.set(true);
347
630
  this.toolMode.set(parsed.toolMode);
348
631
  this.guideOrientation.set(parsed.guideOrientation);
349
632
  this.guides.set(parsed.guides ?? []);
350
- this.showTypography.set(parsed.showTypography ?? true);
633
+ this.showTypography.set(parsed.version === 1 ? false : (parsed.showTypography ?? false));
351
634
  this.recordHistory(parsed.guides ?? []);
352
635
  }
353
636
  }
@@ -357,11 +640,19 @@ class inspectorComponent {
357
640
  this.hydrated.set(true);
358
641
  this.refreshTypographyBlocks();
359
642
  }
643
+ ngOnDestroy() {
644
+ if (!this.isBrowser) {
645
+ return;
646
+ }
647
+ window.removeEventListener('click', this.handleCapturedClick, {
648
+ capture: true,
649
+ });
650
+ }
360
651
  setToolMode(mode) {
361
652
  if (!this.enabled()) {
362
653
  this.enabled.set(true);
363
654
  }
364
- this.toolMode.set(this.toolMode() === mode ? "none" : mode);
655
+ this.toolMode.set(this.toolMode() === mode ? 'none' : mode);
365
656
  this.hoverRect.set(null);
366
657
  this.guidePreview.set(null);
367
658
  this.distanceOverlay.set(null);
@@ -371,12 +662,42 @@ class inspectorComponent {
371
662
  this.guideOrientation.set(orientation);
372
663
  this.guidePreview.set(null);
373
664
  }
665
+ activateGuideOrientation(orientation) {
666
+ const isActive = this.toolMode() === 'guides' && this.guideOrientation() === orientation;
667
+ this.setGuideOrientation(orientation);
668
+ if (isActive || this.toolMode() !== 'guides') {
669
+ this.setToolMode('guides');
670
+ }
671
+ }
374
672
  toggleTypography() {
375
673
  this.showTypography.update((value) => !value);
376
674
  this.refreshTypographyBlocks();
377
675
  }
378
- toggleGuideMenu() {
379
- this.guideMenuOpen.update((value) => !value);
676
+ toggleCheckpoints() {
677
+ this.checkpointsOpen.update((value) => !value);
678
+ }
679
+ disableInspector() {
680
+ this.setInspectorEnabled(false);
681
+ }
682
+ async saveCheckpoint() {
683
+ await this.checkpointRegistry?.save();
684
+ }
685
+ async restoreCheckpoint(checkpoint) {
686
+ await this.checkpointRegistry?.restore(checkpoint.id);
687
+ }
688
+ deleteCheckpoint(checkpoint) {
689
+ this.checkpointRegistry?.delete(checkpoint.id);
690
+ }
691
+ beginRename(checkpoint) {
692
+ this.editingCheckpointId.set(checkpoint.id);
693
+ }
694
+ finishRename(checkpoint, event) {
695
+ const input = event.target;
696
+ this.checkpointRegistry?.rename(checkpoint.id, input.value);
697
+ this.editingCheckpointId.set(null);
698
+ }
699
+ cancelRename() {
700
+ this.editingCheckpointId.set(null);
380
701
  }
381
702
  clearGuides() {
382
703
  this.guides.set([]);
@@ -397,54 +718,50 @@ class inspectorComponent {
397
718
  }
398
719
  handleKeydown(event) {
399
720
  const key = event.key.toLowerCase();
400
- if (key === "m") {
401
- event.preventDefault();
402
- this.enabled.update((value) => !value);
403
- if (!this.enabled()) {
404
- this.toolMode.set("none");
405
- this.hoverRect.set(null);
406
- this.selectedGuideId.set(null);
407
- this.distanceOverlay.set(null);
408
- this.textBlocks.set([]);
409
- this.guidePreview.set(null);
721
+ if (key === 'm') {
722
+ const target = event.target;
723
+ if (target?.matches('input, textarea, select') ||
724
+ target?.isContentEditable) {
725
+ return;
410
726
  }
411
- this.refreshTypographyBlocks();
727
+ event.preventDefault();
728
+ this.setInspectorEnabled(!this.enabled());
412
729
  return;
413
730
  }
414
731
  if (!this.enabled()) {
415
732
  return;
416
733
  }
417
- if ((event.metaKey || event.ctrlKey) && key === "z") {
734
+ if ((event.metaKey || event.ctrlKey) && key === 'z') {
418
735
  event.preventDefault();
419
736
  event.shiftKey ? this.redo() : this.undo();
420
737
  return;
421
738
  }
422
- if (key === "s") {
739
+ if (key === 's') {
423
740
  event.preventDefault();
424
- this.setToolMode("select");
741
+ this.setToolMode('select');
425
742
  return;
426
743
  }
427
- if (key === "g") {
744
+ if (key === 'g') {
428
745
  event.preventDefault();
429
- this.setToolMode("guides");
746
+ this.setToolMode('guides');
430
747
  return;
431
748
  }
432
- if (key === "h") {
749
+ if (key === 'h') {
433
750
  event.preventDefault();
434
- this.setGuideOrientation("horizontal");
751
+ this.setGuideOrientation('horizontal');
435
752
  return;
436
753
  }
437
- if (key === "v") {
754
+ if (key === 'v') {
438
755
  event.preventDefault();
439
- this.setGuideOrientation("vertical");
756
+ this.setGuideOrientation('vertical');
440
757
  return;
441
758
  }
442
- if (event.key === "Alt") {
759
+ if (event.key === 'Alt') {
443
760
  this.altPressed.set(true);
444
761
  this.updateDistanceOverlay();
445
762
  return;
446
763
  }
447
- if (key === "escape") {
764
+ if (key === 'escape') {
448
765
  event.preventDefault();
449
766
  this.selectedMeasurement.set(null);
450
767
  this.selectedGuideId.set(null);
@@ -457,7 +774,7 @@ class inspectorComponent {
457
774
  this.refreshTypographyBlocks();
458
775
  return;
459
776
  }
460
- if ((key === "backspace" || key === "delete") && this.selectedGuideId()) {
777
+ if ((key === 'backspace' || key === 'delete') && this.selectedGuideId()) {
461
778
  event.preventDefault();
462
779
  const nextGuides = this.guides().filter((guide) => guide.id !== this.selectedGuideId());
463
780
  this.guides.set(nextGuides);
@@ -466,7 +783,7 @@ class inspectorComponent {
466
783
  }
467
784
  }
468
785
  handleKeyup(event) {
469
- if (event.key === "Alt") {
786
+ if (event.key === 'Alt') {
470
787
  this.altPressed.set(false);
471
788
  this.distanceOverlay.set(null);
472
789
  }
@@ -482,13 +799,13 @@ class inspectorComponent {
482
799
  if (guide.id !== this.draggingGuideId) {
483
800
  return guide;
484
801
  }
485
- const rawPosition = guide.orientation === "vertical" ? event.clientX : event.clientY;
486
- const max = guide.orientation === "vertical" ? viewport.width : viewport.height;
802
+ const rawPosition = guide.orientation === 'vertical' ? event.clientX : event.clientY;
803
+ const max = guide.orientation === 'vertical' ? viewport.width : viewport.height;
487
804
  return { ...guide, position: clamp(rawPosition, 0, max) };
488
805
  }));
489
806
  return;
490
807
  }
491
- if (this.toolMode() === "guides") {
808
+ if (this.toolMode() === 'guides') {
492
809
  this.hoverRect.set(null);
493
810
  this.hoverElement = null;
494
811
  this.distanceOverlay.set(null);
@@ -501,7 +818,7 @@ class inspectorComponent {
501
818
  return;
502
819
  }
503
820
  this.guidePreview.set(null);
504
- if (this.toolMode() !== "select" || !this.hoverHighlightEnabled) {
821
+ if (this.toolMode() !== 'select' || !this.hoverHighlightEnabled) {
505
822
  this.hoverRect.set(null);
506
823
  this.hoverElement = null;
507
824
  if (!this.altPressed()) {
@@ -535,7 +852,7 @@ class inspectorComponent {
535
852
  return;
536
853
  }
537
854
  const overlay = this.overlayRoot()?.nativeElement ?? null;
538
- if (this.toolMode() === "guides") {
855
+ if (this.toolMode() === 'guides') {
539
856
  if (overlay && overlay.contains(event.target)) {
540
857
  return;
541
858
  }
@@ -544,16 +861,52 @@ class inspectorComponent {
544
861
  this.guidePreview.set(guide);
545
862
  return;
546
863
  }
547
- if (this.toolMode() !== "select") {
864
+ if (!this.canInspectClick(event)) {
548
865
  return;
549
866
  }
867
+ this.selectElementAtPoint(event);
868
+ }
869
+ handleViewportChange() {
870
+ if (this.selectedElement && document.contains(this.selectedElement)) {
871
+ this.selectedMeasurement.set(getInspectMeasurement(this.selectedElement));
872
+ }
873
+ this.updateDistanceOverlay();
874
+ this.refreshTypographyBlocks();
875
+ }
876
+ canInspectClick(event) {
877
+ if (!this.enabled() || !this.isBrowser || this.toolMode() !== 'select') {
878
+ return false;
879
+ }
880
+ const overlay = this.overlayRoot()?.nativeElement ?? null;
881
+ return !(overlay && overlay.contains(event.target));
882
+ }
883
+ setInspectorEnabled(enabled) {
884
+ this.enabled.set(enabled);
885
+ if (!enabled) {
886
+ this.toolMode.set('none');
887
+ this.hoverRect.set(null);
888
+ this.selectedGuideId.set(null);
889
+ this.selectedMeasurement.set(null);
890
+ this.distanceOverlay.set(null);
891
+ this.textBlocks.set([]);
892
+ this.guidePreview.set(null);
893
+ this.checkpointsOpen.set(false);
894
+ this.altPressed.set(false);
895
+ this.selectedElement = null;
896
+ this.hoverElement = null;
897
+ this.draggingGuideId = null;
898
+ }
899
+ this.refreshTypographyBlocks();
900
+ }
901
+ selectElementAtPoint(event) {
902
+ const overlay = this.overlayRoot()?.nativeElement ?? null;
550
903
  const target = getTargetElement({ x: event.clientX, y: event.clientY }, overlay);
551
904
  if (!target) {
552
905
  this.selectedMeasurement.set(null);
553
906
  this.selectedElement = null;
554
907
  this.distanceOverlay.set(null);
555
908
  this.refreshTypographyBlocks();
556
- return;
909
+ return false;
557
910
  }
558
911
  // Ctrl+click: select the minimum common parent of current selection and clicked element
559
912
  if ((event.ctrlKey || event.metaKey) && this.selectedElement) {
@@ -565,7 +918,7 @@ class inspectorComponent {
565
918
  this.selectedElement = commonParent;
566
919
  this.updateDistanceOverlay();
567
920
  this.refreshTypographyBlocks();
568
- return;
921
+ return true;
569
922
  }
570
923
  }
571
924
  this.selectedMeasurement.set(getInspectMeasurement(target));
@@ -573,13 +926,7 @@ class inspectorComponent {
573
926
  this.selectedGuideId.set(this.findGuideAtPoint(event.clientX, event.clientY));
574
927
  this.updateDistanceOverlay();
575
928
  this.refreshTypographyBlocks();
576
- }
577
- handleViewportChange() {
578
- if (this.selectedElement && document.contains(this.selectedElement)) {
579
- this.selectedMeasurement.set(getInspectMeasurement(this.selectedElement));
580
- }
581
- this.updateDistanceOverlay();
582
- this.refreshTypographyBlocks();
929
+ return true;
583
930
  }
584
931
  updateDistanceOverlay() {
585
932
  if (!this.altPressed() ||
@@ -622,10 +969,10 @@ class inspectorComponent {
622
969
  getGuidePreview(event) {
623
970
  const viewport = getViewportSize();
624
971
  const orientation = this.guideOrientation();
625
- const rawPosition = orientation === "vertical" ? event.clientX : event.clientY;
626
- const max = orientation === "vertical" ? viewport.width : viewport.height;
972
+ const rawPosition = orientation === 'vertical' ? event.clientX : event.clientY;
973
+ const max = orientation === 'vertical' ? viewport.width : viewport.height;
627
974
  return {
628
- id: "preview",
975
+ id: 'preview',
629
976
  orientation,
630
977
  position: this.snapGuide(clamp(rawPosition, 0, max)),
631
978
  };
@@ -640,11 +987,11 @@ class inspectorComponent {
640
987
  }
641
988
  findGuideAtPoint(x, y) {
642
989
  for (const guide of this.guides()) {
643
- if (guide.orientation === "vertical" &&
990
+ if (guide.orientation === 'vertical' &&
644
991
  Math.abs(guide.position - x) <= GUIDE_HITBOX_SIZE) {
645
992
  return guide.id;
646
993
  }
647
- if (guide.orientation === "horizontal" &&
994
+ if (guide.orientation === 'horizontal' &&
648
995
  Math.abs(guide.position - y) <= GUIDE_HITBOX_SIZE) {
649
996
  return guide.id;
650
997
  }
@@ -695,11 +1042,11 @@ class inspectorComponent {
695
1042
  return null;
696
1043
  }
697
1044
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: inspectorComponent, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Component }); }
698
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: inspectorComponent, isStandalone: true, selector: "inspector-overlay", inputs: { highlightColor: "highlightColor", guideColor: "guideColor", hoverHighlightEnabled: "hoverHighlightEnabled", persistOnReload: "persistOnReload" }, host: { listeners: { "window:keydown": "handleKeydown($event)", "window:keyup": "handleKeyup($event)", "window:pointermove": "handlePointerMove($event)", "window:pointerup": "handlePointerUp()", "window:click": "handleClick($event)", "window:resize": "handleViewportChange()", "window:scroll": "handleViewportChange()" } }, viewQueries: [{ propertyName: "overlayRoot", first: true, predicate: ["overlayRoot"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"inspector-root\" #overlayRoot>\n <div class=\"inspector-toolbar\">\n <div class=\"inspector-toolbar__rail\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'select'\"\n (click)=\"setToolMode('select')\"\n title=\"Select mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2l8 8-3 .7L6.7 13 6 10 3 2z\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"showTypography()\"\n (click)=\"toggleTypography()\"\n title=\"Typography mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 4V2h10v2h-4v10H7V4z\" />\n </svg>\n </button>\n <div class=\"inspector-guide-group\">\n <button\n type=\"button\"\n class=\"inspector-icon-button inspector-icon-button--guide-main\"\n [class.is-active]=\"toolMode() === 'guides'\"\n (click)=\"setToolMode('guides')\"\n title=\"Guides mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4 2h1v12H4zM11 2h1v12h-1zM2 4h12v1H2zM2 11h12v1H2z\" />\n </svg>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button inspector-icon-button--ghost inspector-icon-button--guide-menu\"\n [class.is-active]=\"guideMenuOpen()\"\n (click)=\"toggleGuideMenu()\"\n title=\"Guide options\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4.47 6.97L8 10.5l3.53-3.53-.94-.94L8 8.62 5.41 6.03z\" />\n </svg>\n </button>\n\n @if (guideMenuOpen()) {\n <div class=\"inspector-guide-menu\">\n <button\n type=\"button\"\n class=\"inspector-segment\"\n [class.is-active]=\"guideOrientation() === 'vertical'\"\n (click)=\"setGuideOrientation('vertical')\"\n >\n Vertical\n </button>\n <button\n type=\"button\"\n class=\"inspector-segment\"\n [class.is-active]=\"guideOrientation() === 'horizontal'\"\n (click)=\"setGuideOrientation('horizontal')\"\n >\n Horizontal\n </button>\n <button\n type=\"button\"\n class=\"inspector-segment inspector-segment--danger\"\n (click)=\"clearGuides()\"\n >\n Clear\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n\n @if (enabled() && toolMode() === \"select\") {\n @if (hoverRect() && hoverHighlightEnabled) {\n <div\n class=\"inspector-box inspector-box--hover\"\n [style.left.px]=\"hoverRect()!.left\"\n [style.top.px]=\"hoverRect()!.top\"\n [style.width.px]=\"hoverRect()!.width\"\n [style.height.px]=\"hoverRect()!.height\"\n [style.--inspector-accent]=\"highlightColor\"\n ></div>\n }\n\n @if (selectedMeasurement(); as selected) {\n @if (selected.gap.active && selected.parentRect) {\n <div\n class=\"inspector-box inspector-box--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top\"\n [style.width.px]=\"selected.parentRect.width\"\n [style.height.px]=\"selected.parentRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top - 8\"\n >\n G {{ formatGap(selected) }}\n </div>\n }\n\n <div\n class=\"inspector-box inspector-box--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top\"\n [style.width.px]=\"selected.marginRect.width\"\n [style.height.px]=\"selected.marginRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top - 8\"\n >\n M {{ formatEdges(selected.margin) }}\n </div>\n\n <div\n class=\"inspector-box inspector-box--selected\"\n [style.left.px]=\"selected.rect.left\"\n [style.top.px]=\"selected.rect.top\"\n [style.width.px]=\"selected.rect.width\"\n [style.height.px]=\"selected.rect.height\"\n [style.--inspector-accent]=\"highlightColor\"\n >\n <div class=\"inspector-label\">{{ selectedMetaLine() }}</div>\n </div>\n\n <div\n class=\"inspector-box inspector-box--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"selected.paddingRect.top\"\n [style.width.px]=\"selected.paddingRect.width\"\n [style.height.px]=\"selected.paddingRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"\n selected.paddingRect.top + selected.paddingRect.height + 8\n \"\n >\n P {{ formatEdges(selected.padding) }}\n </div>\n }\n }\n\n @if (enabled() && showTypography()) {\n @for (block of textBlocks(); track block.id) {\n <div\n class=\"inspector-text-chip\"\n [style.left.px]=\"block.rect.left\"\n [style.top.px]=\"block.rect.top - 8\"\n >\n <span\n class=\"inspector-text-chip__swatch\"\n [style.background]=\"block.styles.color\"\n ></span>\n <span>{{ block.styles.fontSize }}</span>\n <span>{{ block.styles.lineHeight }}</span>\n <span>{{ block.styles.color }}</span>\n </div>\n }\n }\n\n @if (enabled() && altPressed() && distanceOverlay(); as distance) {\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectA.left\"\n [style.top.px]=\"distance.rectA.top\"\n [style.width.px]=\"distance.rectA.width\"\n [style.height.px]=\"distance.rectA.height\"\n ></div>\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectB.left\"\n [style.top.px]=\"distance.rectB.top\"\n [style.width.px]=\"distance.rectB.width\"\n [style.height.px]=\"distance.rectB.height\"\n ></div>\n\n @for (\n connector of distance.connectors;\n track trackConnector($index, connector)\n ) {\n <div\n class=\"inspector-connector\"\n [class.inspector-connector--vertical]=\"connector.x1 === connector.x2\"\n [class.inspector-connector--horizontal]=\"connector.y1 === connector.y2\"\n [style.left.px]=\"Math.min(connector.x1, connector.x2)\"\n [style.top.px]=\"Math.min(connector.y1, connector.y2)\"\n [style.width.px]=\"Math.max(1, Math.abs(connector.x2 - connector.x1))\"\n [style.height.px]=\"Math.max(1, Math.abs(connector.y2 - connector.y1))\"\n ></div>\n }\n\n @if (distance.horizontal && distance.horizontal.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--horizontal\"\n [style.left.px]=\"\n Math.min(distance.horizontal.x1!, distance.horizontal.x2!)\n \"\n [style.top.px]=\"distance.horizontal.y!\"\n [style.width.px]=\"\n Math.abs(distance.horizontal.x2! - distance.horizontal.x1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag\"\n [style.left.px]=\"\n (distance.horizontal.x1! + distance.horizontal.x2!) / 2\n \"\n [style.top.px]=\"distance.horizontal.y! + 10\"\n >\n {{ formatValue(distance.horizontal.value) }}\n </div>\n }\n\n @if (distance.vertical && distance.vertical.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--vertical\"\n [style.left.px]=\"distance.vertical.x!\"\n [style.top.px]=\"Math.min(distance.vertical.y1!, distance.vertical.y2!)\"\n [style.height.px]=\"\n Math.abs(distance.vertical.y2! - distance.vertical.y1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag inspector-distance-tag--vertical\"\n [style.left.px]=\"distance.vertical.x! + 10\"\n [style.top.px]=\"(distance.vertical.y1! + distance.vertical.y2!) / 2\"\n >\n {{ formatValue(distance.vertical.value) }}\n </div>\n }\n }\n\n @if (enabled()) {\n @if (toolMode() === \"guides\") {\n @if (guidePreview(); as preview) {\n <div\n class=\"inspector-guide inspector-guide--preview\"\n [class.inspector-guide--vertical]=\"preview.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"\n preview.orientation === 'horizontal'\n \"\n [style.left.px]=\"\n preview.orientation === 'vertical' ? preview.position : null\n \"\n [style.top.px]=\"\n preview.orientation === 'horizontal' ? preview.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n >\n <span class=\"inspector-guide-line\"></span>\n </div>\n }\n }\n\n @for (guide of guides(); track guide.id) {\n <button\n type=\"button\"\n class=\"inspector-guide\"\n [class.inspector-guide--vertical]=\"guide.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"guide.orientation === 'horizontal'\"\n [class.inspector-guide--selected]=\"selectedGuideId() === guide.id\"\n [style.left.px]=\"\n guide.orientation === 'vertical' ? guide.position : null\n \"\n [style.top.px]=\"\n guide.orientation === 'horizontal' ? guide.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n (pointerdown)=\"startGuideDrag($event, guide)\"\n >\n <span class=\"inspector-guide-line\"></span>\n </button>\n }\n }\n</div>\n", styles: [":host{--inspector-panel: rgba(27, 29, 36, .94);--inspector-panel-edge: rgba(255, 255, 255, .08);--inspector-text: #f3f4f6;--inspector-muted: #9ca3af;--inspector-blue: #4f8cff;--inspector-orange: #f59e0b;--inspector-green: #22c55e;--inspector-violet: #a855f7;position:fixed;inset:0;z-index:2147;pointer-events:none;font-family:SF Pro Display,Segoe UI,sans-serif}.inspector-root{position:fixed;inset:0;pointer-events:none}.inspector-toolbar{position:fixed;top:18px;right:18px;display:block;pointer-events:auto;z-index:10000}.inspector-toolbar__rail,.inspector-guide-menu{display:flex;gap:4px;padding:4px;border-radius:8px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.inspector-toolbar__rail{align-items:center}.inspector-guide-group{position:relative;display:flex;gap:0;align-items:center;border-radius:6px}.inspector-guide-menu{position:absolute;top:calc(100% + 8px);right:0;min-width:max-content;flex-direction:column;align-items:stretch}.inspector-guide-menu .inspector-segment{justify-content:flex-start;width:100%}.inspector-icon-button,.inspector-segment{display:inline-flex;align-items:center;justify-content:center;height:26px;border:0;border-radius:6px;background:transparent;color:var(--inspector-muted);cursor:pointer;transition:background .12s ease,color .12s ease;font:inherit;font-size:11px;letter-spacing:.02em}.inspector-icon-button{width:26px}.inspector-icon-button svg{width:14px;height:14px;fill:currentColor}.inspector-icon-button:hover,.inspector-segment:hover{background:#ffffff14;color:var(--inspector-text)}.inspector-icon-button.is-active,.inspector-segment.is-active{background:#4f8cff2e;color:#dbeafe}.inspector-icon-button--ghost.is-active{background:#ffffff14;color:var(--inspector-text)}.inspector-icon-button--guide-main{border-radius:6px 0 0 6px}.inspector-icon-button--guide-menu{width:20px;border-radius:0 6px 6px 0}.inspector-icon-button--guide-menu svg{width:12px;height:12px}.inspector-segment{padding:0 8px;white-space:nowrap}.inspector-segment--danger:hover{background:#ef444429;color:#fecaca}.inspector-box{position:fixed;box-sizing:border-box;pointer-events:none;border:1px solid var(--inspector-accent, var(--inspector-blue));background:color-mix(in srgb,var(--inspector-accent, var(--inspector-blue)) 8%,transparent)}.inspector-box--hover{opacity:.55}.inspector-box--selected{border-width:1.5px}.inspector-box--margin{border-color:#f59e0bd1;background:#f59e0b14}.inspector-box--padding{border-color:#22c55ed1;background:#22c55e14}.inspector-box--gap{border-color:#a855f7b8;background:#a855f70a}.inspector-box--distance{border-style:dashed;border-color:#4f8cffb3;background:transparent}.inspector-label,.inspector-box-tag,.inspector-text-chip,.inspector-distance-tag{font-variant-numeric:tabular-nums}.inspector-label{position:absolute;left:0;top:0;transform:translateY(calc(-100% - 8px));max-width:min(420px,calc(100vw - 24px));padding:6px 9px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);color:var(--inspector-text);box-shadow:0 18px 40px #00000042;font-size:11px;line-height:1;white-space:nowrap}.inspector-box-tag{position:fixed;transform:translateY(-100%);padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:var(--inspector-text);box-shadow:0 12px 24px #0003;font-size:10px;line-height:1;pointer-events:none}.inspector-box-tag--margin{color:#fbbf24}.inspector-box-tag--padding{color:#86efac;transform:translateY(0)}.inspector-box-tag--gap{color:#d8b4fe}.inspector-text-chip{position:fixed;transform:translateY(-100%);display:inline-flex;gap:6px;align-items:center;padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:#e5e7eb;box-shadow:0 12px 24px #00000038;font-size:10px;line-height:1;white-space:nowrap;pointer-events:none}.inspector-text-chip__swatch{width:8px;height:8px;border-radius:999px;border:1px solid rgba(255,255,255,.18);flex:0 0 auto}.inspector-connector{position:fixed;border-color:#4f8cffb8;border-style:dashed;pointer-events:none}.inspector-connector--vertical{border-left-width:1px;width:0}.inspector-connector--horizontal{border-top-width:1px;height:0}.inspector-distance-line{position:fixed;background:var(--inspector-blue);pointer-events:none}.inspector-distance-line--horizontal{height:1px}.inspector-distance-line--vertical{width:1px}.inspector-distance-tag{position:fixed;transform:translate(-50%);padding:5px 7px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f2;color:var(--inspector-text);font-size:10px;line-height:1;pointer-events:none}.inspector-distance-tag--vertical{transform:translateY(-50%)}.inspector-guide{position:fixed;margin:0;padding:0;border:0;background:transparent;pointer-events:auto;cursor:grab}.inspector-guide--vertical{top:0;width:14px;height:100vh;transform:translate(-50%)}.inspector-guide--horizontal{left:0;width:100vw;height:14px;transform:translateY(-50%)}.inspector-guide--preview{opacity:.55;pointer-events:none;cursor:default}.inspector-guide--preview .inspector-guide-line{background:color-mix(in srgb,var(--inspector-guide, #ff7a00) 72%,transparent)}.inspector-guide-line{position:absolute;inset:0;pointer-events:none}.inspector-guide--vertical .inspector-guide-line{left:calc(50% - .5px);width:1px;height:100%;background:var(--inspector-guide, #ff7a00)}.inspector-guide--horizontal .inspector-guide-line{top:calc(50% - .5px);width:100%;height:1px;background:var(--inspector-guide, #ff7a00)}.inspector-guide--selected .inspector-guide-line{box-shadow:0 0 0 1px #ffffff80}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1045
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: inspectorComponent, isStandalone: true, selector: "inspector-overlay", inputs: { highlightColor: "highlightColor", guideColor: "guideColor", hoverHighlightEnabled: "hoverHighlightEnabled", persistOnReload: "persistOnReload" }, host: { listeners: { "window:keydown": "handleKeydown($event)", "window:keyup": "handleKeyup($event)", "window:pointermove": "handlePointerMove($event)", "window:pointerup": "handlePointerUp()", "window:click": "handleClick($event)", "window:resize": "handleViewportChange()", "window:scroll": "handleViewportChange()" } }, viewQueries: [{ propertyName: "overlayRoot", first: true, predicate: ["overlayRoot"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"inspector-root\" #overlayRoot>\n @if (enabled()) {\n <div class=\"inspector-toolbar\">\n <div class=\"inspector-toolbar__rail\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'select'\"\n (click)=\"setToolMode('select')\"\n aria-label=\"Select mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2l8 8-3 .7L6.7 13 6 10 3 2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Select</span>\n </button>\n\n @if (checkpointRegistry) {\n <div class=\"inspector-checkpoint-group\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"checkpointsOpen()\"\n (click)=\"toggleCheckpoints()\"\n aria-label=\"Checkpoints\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2.5h10v11H3zm2 2v2h6v-2zm0 4v2h4v-2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Checkpoints</span>\n </button>\n\n @if (checkpointsOpen()) {\n <section class=\"inspector-checkpoint-panel\" aria-label=\"Saved checkpoints\">\n <header class=\"inspector-checkpoint-panel__header\">\n <div>\n <strong>Checkpoints</strong>\n <span>{{ checkpointRegistry.checkpoints().length }}</span>\n </div>\n <button\n type=\"button\"\n class=\"inspector-checkpoint-save\"\n (click)=\"saveCheckpoint()\"\n [disabled]=\"checkpointRegistry.isBusy()\"\n >\n {{ checkpointRegistry.isBusy() ? 'Working\u2026' : 'Save current' }}\n </button>\n </header>\n\n @if (checkpointRegistry.error(); as error) {\n <p class=\"inspector-checkpoint-message inspector-checkpoint-message--error\">{{ error }}</p>\n }\n @if (checkpointRegistry.warning(); as warning) {\n <p class=\"inspector-checkpoint-message\">{{ warning }}</p>\n }\n\n @if (checkpointRegistry.checkpoints().length) {\n <div class=\"inspector-checkpoint-list\">\n @for (checkpoint of checkpointRegistry.checkpoints(); track checkpoint.id) {\n <article class=\"inspector-checkpoint-item\">\n <div class=\"inspector-checkpoint-item__main\">\n @if (editingCheckpointId() === checkpoint.id) {\n <input\n #checkpointName\n class=\"inspector-checkpoint-name-input\"\n [value]=\"checkpoint.name\"\n (keydown.enter)=\"finishRename(checkpoint, $event)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"finishRename(checkpoint, $event)\"\n aria-label=\"Checkpoint name\"\n autofocus\n />\n } @else {\n <button type=\"button\" class=\"inspector-checkpoint-name\" (click)=\"beginRename(checkpoint)\">\n {{ checkpoint.name }}\n </button>\n }\n </div>\n <div class=\"inspector-checkpoint-actions\">\n <button type=\"button\" (click)=\"restoreCheckpoint(checkpoint)\" [disabled]=\"checkpointRegistry.isBusy()\">Restore</button>\n <button type=\"button\" (click)=\"deleteCheckpoint(checkpoint)\" [disabled]=\"checkpointRegistry.isBusy()\">Delete</button>\n </div>\n </article>\n }\n </div>\n } @else {\n <p class=\"inspector-checkpoint-empty\">No checkpoints yet.</p>\n }\n </section>\n }\n </div>\n }\n\n <div class=\"inspector-toolbar__secondary\">\n <div class=\"inspector-toolbar__reveal\">\n <div class=\"inspector-toolbar__reveal-inner\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"showTypography()\"\n (click)=\"toggleTypography()\"\n aria-label=\"Typography overlay\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 4V2h10v2h-4v10H7V4z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Typography</span>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'guides' && guideOrientation() === 'vertical'\"\n (click)=\"activateGuideOrientation('vertical')\"\n aria-label=\"Vertical guides\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M7.25 2h1.5v12h-1.5z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Vertical guides</span>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'guides' && guideOrientation() === 'horizontal'\"\n (click)=\"activateGuideOrientation('horizontal')\"\n aria-label=\"Horizontal guides\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M2 7.25h12v1.5H2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Horizontal guides</span>\n </button>\n </div>\n </div>\n </div>\n\n <span class=\"inspector-toolbar__divider\" aria-hidden=\"true\"></span>\n <button\n type=\"button\"\n class=\"inspector-icon-button inspector-icon-button--power\"\n (click)=\"disableInspector()\"\n aria-label=\"Disable inspector; press M to enable\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M7.25 1.5h1.5v6h-1.5zM4.1 3.35l.96 1.15A4.25 4.25 0 1 0 10.94 4.5l.96-1.15A5.75 5.75 0 1 1 4.1 3.35z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Disable</span>\n </button>\n </div>\n </div>\n }\n\n @if (enabled() && toolMode() === \"select\") {\n @if (hoverRect() && hoverHighlightEnabled) {\n <div\n class=\"inspector-box inspector-box--hover\"\n [style.left.px]=\"hoverRect()!.left\"\n [style.top.px]=\"hoverRect()!.top\"\n [style.width.px]=\"hoverRect()!.width\"\n [style.height.px]=\"hoverRect()!.height\"\n [style.--inspector-accent]=\"highlightColor\"\n ></div>\n }\n\n @if (selectedMeasurement(); as selected) {\n @if (selected.gap.active && selected.parentRect) {\n <div\n class=\"inspector-box inspector-box--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top\"\n [style.width.px]=\"selected.parentRect.width\"\n [style.height.px]=\"selected.parentRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top - 8\"\n >\n G {{ formatGap(selected) }}\n </div>\n }\n\n <div\n class=\"inspector-box inspector-box--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top\"\n [style.width.px]=\"selected.marginRect.width\"\n [style.height.px]=\"selected.marginRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top - 8\"\n >\n M {{ formatEdges(selected.margin) }}\n </div>\n\n <div\n class=\"inspector-box inspector-box--selected\"\n [style.left.px]=\"selected.rect.left\"\n [style.top.px]=\"selected.rect.top\"\n [style.width.px]=\"selected.rect.width\"\n [style.height.px]=\"selected.rect.height\"\n [style.--inspector-accent]=\"highlightColor\"\n >\n <div class=\"inspector-label\">{{ selectedMetaLine() }}</div>\n </div>\n\n <div\n class=\"inspector-box inspector-box--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"selected.paddingRect.top\"\n [style.width.px]=\"selected.paddingRect.width\"\n [style.height.px]=\"selected.paddingRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"\n selected.paddingRect.top + selected.paddingRect.height + 8\n \"\n >\n P {{ formatEdges(selected.padding) }}\n </div>\n }\n }\n\n @if (enabled() && showTypography()) {\n @for (block of textBlocks(); track block.id) {\n <div\n class=\"inspector-text-chip\"\n [style.left.px]=\"block.rect.left\"\n [style.top.px]=\"block.rect.top - 8\"\n >\n <span\n class=\"inspector-text-chip__swatch\"\n [style.background]=\"block.styles.color\"\n ></span>\n <span>{{ block.styles.fontSize }}</span>\n <span>{{ block.styles.lineHeight }}</span>\n <span>{{ block.styles.color }}</span>\n </div>\n }\n }\n\n @if (enabled() && altPressed() && distanceOverlay(); as distance) {\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectA.left\"\n [style.top.px]=\"distance.rectA.top\"\n [style.width.px]=\"distance.rectA.width\"\n [style.height.px]=\"distance.rectA.height\"\n ></div>\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectB.left\"\n [style.top.px]=\"distance.rectB.top\"\n [style.width.px]=\"distance.rectB.width\"\n [style.height.px]=\"distance.rectB.height\"\n ></div>\n\n @for (\n connector of distance.connectors;\n track trackConnector($index, connector)\n ) {\n <div\n class=\"inspector-connector\"\n [class.inspector-connector--vertical]=\"connector.x1 === connector.x2\"\n [class.inspector-connector--horizontal]=\"connector.y1 === connector.y2\"\n [style.left.px]=\"Math.min(connector.x1, connector.x2)\"\n [style.top.px]=\"Math.min(connector.y1, connector.y2)\"\n [style.width.px]=\"Math.max(1, Math.abs(connector.x2 - connector.x1))\"\n [style.height.px]=\"Math.max(1, Math.abs(connector.y2 - connector.y1))\"\n ></div>\n }\n\n @if (distance.horizontal && distance.horizontal.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--horizontal\"\n [style.left.px]=\"\n Math.min(distance.horizontal.x1!, distance.horizontal.x2!)\n \"\n [style.top.px]=\"distance.horizontal.y!\"\n [style.width.px]=\"\n Math.abs(distance.horizontal.x2! - distance.horizontal.x1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag\"\n [style.left.px]=\"\n (distance.horizontal.x1! + distance.horizontal.x2!) / 2\n \"\n [style.top.px]=\"distance.horizontal.y! + 10\"\n >\n {{ formatValue(distance.horizontal.value) }}\n </div>\n }\n\n @if (distance.vertical && distance.vertical.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--vertical\"\n [style.left.px]=\"distance.vertical.x!\"\n [style.top.px]=\"Math.min(distance.vertical.y1!, distance.vertical.y2!)\"\n [style.height.px]=\"\n Math.abs(distance.vertical.y2! - distance.vertical.y1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag inspector-distance-tag--vertical\"\n [style.left.px]=\"distance.vertical.x! + 10\"\n [style.top.px]=\"(distance.vertical.y1! + distance.vertical.y2!) / 2\"\n >\n {{ formatValue(distance.vertical.value) }}\n </div>\n }\n }\n\n @if (enabled()) {\n @if (toolMode() === \"guides\") {\n @if (guidePreview(); as preview) {\n <div\n class=\"inspector-guide inspector-guide--preview\"\n [class.inspector-guide--vertical]=\"preview.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"\n preview.orientation === 'horizontal'\n \"\n [style.left.px]=\"\n preview.orientation === 'vertical' ? preview.position : null\n \"\n [style.top.px]=\"\n preview.orientation === 'horizontal' ? preview.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n >\n <span class=\"inspector-guide-line\"></span>\n </div>\n }\n }\n\n @for (guide of guides(); track guide.id) {\n <button\n type=\"button\"\n class=\"inspector-guide\"\n [class.inspector-guide--vertical]=\"guide.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"guide.orientation === 'horizontal'\"\n [class.inspector-guide--selected]=\"selectedGuideId() === guide.id\"\n [style.left.px]=\"\n guide.orientation === 'vertical' ? guide.position : null\n \"\n [style.top.px]=\"\n guide.orientation === 'horizontal' ? guide.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n (pointerdown)=\"startGuideDrag($event, guide)\"\n >\n <span class=\"inspector-guide-line\"></span>\n </button>\n }\n }\n</div>\n", styles: [":host{--inspector-panel: rgba(27, 29, 36, .94);--inspector-panel-edge: rgba(255, 255, 255, .08);--inspector-text: #f3f4f6;--inspector-muted: #9ca3af;--inspector-blue: #4f8cff;--inspector-orange: #f59e0b;--inspector-green: #22c55e;--inspector-violet: #a855f7;position:fixed;inset:0;z-index:2147;pointer-events:none;font-family:SF Pro Display,Segoe UI,sans-serif}.inspector-root{position:fixed;inset:0;pointer-events:none}.inspector-toolbar{position:fixed;top:12px;right:12px;display:block;pointer-events:auto;z-index:10000}.inspector-toolbar__rail{display:flex;flex-direction:column;align-items:center;gap:2px;width:30px;padding:2px;border-radius:4px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.inspector-toolbar__divider{width:16px;height:1px;margin-block:2px;background:var(--inspector-panel-edge)}.inspector-toolbar__secondary{display:grid;width:24px}.inspector-toolbar__reveal{display:grid;grid-template-rows:0fr;opacity:0;transform:translateY(-4px);transition:grid-template-rows .18s cubic-bezier(.16,1,.3,1),opacity .12s cubic-bezier(.16,1,.3,1),transform .18s cubic-bezier(.16,1,.3,1)}.inspector-toolbar__reveal-inner{display:grid;gap:2px;min-height:0;overflow:hidden}.inspector-toolbar__rail:hover .inspector-toolbar__reveal,.inspector-toolbar__rail:focus-within .inspector-toolbar__reveal{grid-template-rows:1fr;opacity:1;transform:translateY(2px)}.inspector-checkpoint-group{position:relative;width:24px;height:24px}.inspector-checkpoint-panel{position:absolute;top:0;right:calc(100% + 8px);width:min(300px,calc(100vw - 24px));max-height:min(420px,calc(100vh - 64px));overflow:auto;border:1px solid var(--inspector-panel-edge);border-radius:8px;background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;color:var(--inspector-text)}.inspector-checkpoint-panel__header,.inspector-checkpoint-item,.inspector-checkpoint-actions{display:flex;align-items:center}.inspector-checkpoint-panel__header{justify-content:space-between;gap:8px;padding:6px 8px;border-bottom:1px solid var(--inspector-panel-edge)}.inspector-checkpoint-panel__header strong,.inspector-checkpoint-panel__header span{display:inline}.inspector-checkpoint-panel__header strong{font-size:12px}.inspector-checkpoint-panel__header span{margin-left:5px}.inspector-checkpoint-panel__header span,.inspector-checkpoint-empty,.inspector-checkpoint-message{color:var(--inspector-muted);font-size:10px;line-height:1.4}.inspector-checkpoint-save,.inspector-checkpoint-actions button,.inspector-checkpoint-name{border:0;background:transparent;color:inherit;cursor:pointer;font:inherit}.inspector-checkpoint-save{padding:5px 7px;border-radius:5px;background:#4f8cff2e;color:#dbeafe;font-size:10px}.inspector-checkpoint-list{display:grid}.inspector-checkpoint-item{justify-content:space-between;gap:8px;padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.05)}.inspector-checkpoint-item__main{flex:1 1 auto;min-width:0}.inspector-checkpoint-name,.inspector-checkpoint-name-input{display:block;max-width:100%;overflow:hidden;padding:0;color:var(--inspector-text);font-size:11px;font-weight:600;text-align:left;text-overflow:ellipsis;white-space:nowrap}.inspector-checkpoint-name-input{width:100%;border:1px solid rgba(79,140,255,.6);border-radius:3px;background:#0003;outline:none}.inspector-checkpoint-actions{gap:2px;flex:0 0 auto}.inspector-checkpoint-actions button{padding:4px;border-radius:4px;color:var(--inspector-muted);font-size:10px}.inspector-checkpoint-actions button:hover,.inspector-checkpoint-name:hover,.inspector-checkpoint-save:hover{color:var(--inspector-text);background:#ffffff14}.inspector-checkpoint-empty,.inspector-checkpoint-message{margin:0;padding:12px 10px}.inspector-checkpoint-message--error{color:#fecaca}.inspector-icon-button{display:inline-flex;align-items:center;justify-content:center;height:24px;border:0;border-radius:3px;background:transparent;color:var(--inspector-muted);cursor:pointer;transition:background .1s cubic-bezier(.16,1,.3,1),color .1s cubic-bezier(.16,1,.3,1),transform .1s cubic-bezier(.16,1,.3,1);font:inherit;font-size:11px;letter-spacing:.02em}.inspector-icon-button{position:relative;width:24px}.inspector-icon-button svg{width:13px;height:13px;fill:currentColor}.inspector-icon-button:active{transform:translateY(1px)}.inspector-icon-button:focus-visible{outline:2px solid var(--inspector-blue);outline-offset:2px}.inspector-icon-button:hover{background:#ffffff14;color:var(--inspector-text)}.inspector-icon-button.is-active{background:#4f8cff2e;color:#dbeafe}.inspector-icon-button--power:hover{background:#ef444429;color:#fecaca}.inspector-tooltip{position:absolute;top:50%;right:calc(100% + 7px);z-index:20;width:max-content;padding:4px 6px;border:1px solid var(--inspector-panel-edge);border-radius:3px;background:#111318fa;color:var(--inspector-text);font-size:10px;font-weight:600;line-height:1.2;opacity:0;pointer-events:none;transform:translate(3px,-50%);transition:opacity .12s cubic-bezier(.16,1,.3,1),transform .12s cubic-bezier(.16,1,.3,1);white-space:nowrap}.inspector-icon-button:hover .inspector-tooltip,.inspector-icon-button:focus-visible .inspector-tooltip{opacity:1;transform:translateY(-50%)}@media (prefers-reduced-motion: reduce){.inspector-toolbar__reveal,.inspector-icon-button,.inspector-tooltip{transition-duration:.01ms}}.inspector-box{position:fixed;box-sizing:border-box;pointer-events:none;border:1px solid var(--inspector-accent, var(--inspector-blue));background:color-mix(in srgb,var(--inspector-accent, var(--inspector-blue)) 8%,transparent)}.inspector-box--hover{opacity:.55}.inspector-box--selected{border-width:1.5px}.inspector-box--margin{border-color:#f59e0bd1;background:#f59e0b14}.inspector-box--padding{border-color:#22c55ed1;background:#22c55e14}.inspector-box--gap{border-color:#a855f7b8;background:#a855f70a}.inspector-box--distance{border-style:dashed;border-color:#4f8cffb3;background:transparent}.inspector-label,.inspector-box-tag,.inspector-text-chip,.inspector-distance-tag{font-variant-numeric:tabular-nums}.inspector-label{position:absolute;left:0;top:0;transform:translateY(calc(-100% - 8px));max-width:min(420px,calc(100vw - 24px));padding:6px 9px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);color:var(--inspector-text);box-shadow:0 18px 40px #00000042;font-size:11px;line-height:1;white-space:nowrap}.inspector-box-tag{position:fixed;transform:translateY(-100%);padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:var(--inspector-text);box-shadow:0 12px 24px #0003;font-size:10px;line-height:1;pointer-events:none}.inspector-box-tag--margin{color:#fbbf24}.inspector-box-tag--padding{color:#86efac;transform:translateY(0)}.inspector-box-tag--gap{color:#d8b4fe}.inspector-text-chip{position:fixed;transform:translateY(-100%);display:inline-flex;gap:6px;align-items:center;padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:#e5e7eb;box-shadow:0 12px 24px #00000038;font-size:10px;line-height:1;white-space:nowrap;pointer-events:none}.inspector-text-chip__swatch{width:8px;height:8px;border-radius:999px;border:1px solid rgba(255,255,255,.18);flex:0 0 auto}.inspector-connector{position:fixed;border-color:#4f8cffb8;border-style:dashed;pointer-events:none}.inspector-connector--vertical{border-left-width:1px;width:0}.inspector-connector--horizontal{border-top-width:1px;height:0}.inspector-distance-line{position:fixed;background:var(--inspector-blue);pointer-events:none}.inspector-distance-line--horizontal{height:1px}.inspector-distance-line--vertical{width:1px}.inspector-distance-tag{position:fixed;transform:translate(-50%);padding:5px 7px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f2;color:var(--inspector-text);font-size:10px;line-height:1;pointer-events:none}.inspector-distance-tag--vertical{transform:translateY(-50%)}.inspector-guide{position:fixed;margin:0;padding:0;border:0;background:transparent;pointer-events:auto;cursor:grab}.inspector-guide--vertical{top:0;width:14px;height:100vh;transform:translate(-50%)}.inspector-guide--horizontal{left:0;width:100vw;height:14px;transform:translateY(-50%)}.inspector-guide--preview{opacity:.55;pointer-events:none;cursor:default}.inspector-guide--preview .inspector-guide-line{background:color-mix(in srgb,var(--inspector-guide, #ff7a00) 72%,transparent)}.inspector-guide-line{position:absolute;inset:0;pointer-events:none}.inspector-guide--vertical .inspector-guide-line{left:calc(50% - .5px);width:1px;height:100%;background:var(--inspector-guide, #ff7a00)}.inspector-guide--horizontal .inspector-guide-line{top:calc(50% - .5px);width:100%;height:1px;background:var(--inspector-guide, #ff7a00)}.inspector-guide--selected .inspector-guide-line{box-shadow:0 0 0 1px #ffffff80}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
699
1046
  }
700
1047
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: inspectorComponent, decorators: [{
701
1048
  type: Component,
702
- args: [{ selector: "inspector-overlay", standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"inspector-root\" #overlayRoot>\n <div class=\"inspector-toolbar\">\n <div class=\"inspector-toolbar__rail\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'select'\"\n (click)=\"setToolMode('select')\"\n title=\"Select mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2l8 8-3 .7L6.7 13 6 10 3 2z\" />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"showTypography()\"\n (click)=\"toggleTypography()\"\n title=\"Typography mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 4V2h10v2h-4v10H7V4z\" />\n </svg>\n </button>\n <div class=\"inspector-guide-group\">\n <button\n type=\"button\"\n class=\"inspector-icon-button inspector-icon-button--guide-main\"\n [class.is-active]=\"toolMode() === 'guides'\"\n (click)=\"setToolMode('guides')\"\n title=\"Guides mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4 2h1v12H4zM11 2h1v12h-1zM2 4h12v1H2zM2 11h12v1H2z\" />\n </svg>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button inspector-icon-button--ghost inspector-icon-button--guide-menu\"\n [class.is-active]=\"guideMenuOpen()\"\n (click)=\"toggleGuideMenu()\"\n title=\"Guide options\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4.47 6.97L8 10.5l3.53-3.53-.94-.94L8 8.62 5.41 6.03z\" />\n </svg>\n </button>\n\n @if (guideMenuOpen()) {\n <div class=\"inspector-guide-menu\">\n <button\n type=\"button\"\n class=\"inspector-segment\"\n [class.is-active]=\"guideOrientation() === 'vertical'\"\n (click)=\"setGuideOrientation('vertical')\"\n >\n Vertical\n </button>\n <button\n type=\"button\"\n class=\"inspector-segment\"\n [class.is-active]=\"guideOrientation() === 'horizontal'\"\n (click)=\"setGuideOrientation('horizontal')\"\n >\n Horizontal\n </button>\n <button\n type=\"button\"\n class=\"inspector-segment inspector-segment--danger\"\n (click)=\"clearGuides()\"\n >\n Clear\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n\n @if (enabled() && toolMode() === \"select\") {\n @if (hoverRect() && hoverHighlightEnabled) {\n <div\n class=\"inspector-box inspector-box--hover\"\n [style.left.px]=\"hoverRect()!.left\"\n [style.top.px]=\"hoverRect()!.top\"\n [style.width.px]=\"hoverRect()!.width\"\n [style.height.px]=\"hoverRect()!.height\"\n [style.--inspector-accent]=\"highlightColor\"\n ></div>\n }\n\n @if (selectedMeasurement(); as selected) {\n @if (selected.gap.active && selected.parentRect) {\n <div\n class=\"inspector-box inspector-box--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top\"\n [style.width.px]=\"selected.parentRect.width\"\n [style.height.px]=\"selected.parentRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top - 8\"\n >\n G {{ formatGap(selected) }}\n </div>\n }\n\n <div\n class=\"inspector-box inspector-box--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top\"\n [style.width.px]=\"selected.marginRect.width\"\n [style.height.px]=\"selected.marginRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top - 8\"\n >\n M {{ formatEdges(selected.margin) }}\n </div>\n\n <div\n class=\"inspector-box inspector-box--selected\"\n [style.left.px]=\"selected.rect.left\"\n [style.top.px]=\"selected.rect.top\"\n [style.width.px]=\"selected.rect.width\"\n [style.height.px]=\"selected.rect.height\"\n [style.--inspector-accent]=\"highlightColor\"\n >\n <div class=\"inspector-label\">{{ selectedMetaLine() }}</div>\n </div>\n\n <div\n class=\"inspector-box inspector-box--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"selected.paddingRect.top\"\n [style.width.px]=\"selected.paddingRect.width\"\n [style.height.px]=\"selected.paddingRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"\n selected.paddingRect.top + selected.paddingRect.height + 8\n \"\n >\n P {{ formatEdges(selected.padding) }}\n </div>\n }\n }\n\n @if (enabled() && showTypography()) {\n @for (block of textBlocks(); track block.id) {\n <div\n class=\"inspector-text-chip\"\n [style.left.px]=\"block.rect.left\"\n [style.top.px]=\"block.rect.top - 8\"\n >\n <span\n class=\"inspector-text-chip__swatch\"\n [style.background]=\"block.styles.color\"\n ></span>\n <span>{{ block.styles.fontSize }}</span>\n <span>{{ block.styles.lineHeight }}</span>\n <span>{{ block.styles.color }}</span>\n </div>\n }\n }\n\n @if (enabled() && altPressed() && distanceOverlay(); as distance) {\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectA.left\"\n [style.top.px]=\"distance.rectA.top\"\n [style.width.px]=\"distance.rectA.width\"\n [style.height.px]=\"distance.rectA.height\"\n ></div>\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectB.left\"\n [style.top.px]=\"distance.rectB.top\"\n [style.width.px]=\"distance.rectB.width\"\n [style.height.px]=\"distance.rectB.height\"\n ></div>\n\n @for (\n connector of distance.connectors;\n track trackConnector($index, connector)\n ) {\n <div\n class=\"inspector-connector\"\n [class.inspector-connector--vertical]=\"connector.x1 === connector.x2\"\n [class.inspector-connector--horizontal]=\"connector.y1 === connector.y2\"\n [style.left.px]=\"Math.min(connector.x1, connector.x2)\"\n [style.top.px]=\"Math.min(connector.y1, connector.y2)\"\n [style.width.px]=\"Math.max(1, Math.abs(connector.x2 - connector.x1))\"\n [style.height.px]=\"Math.max(1, Math.abs(connector.y2 - connector.y1))\"\n ></div>\n }\n\n @if (distance.horizontal && distance.horizontal.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--horizontal\"\n [style.left.px]=\"\n Math.min(distance.horizontal.x1!, distance.horizontal.x2!)\n \"\n [style.top.px]=\"distance.horizontal.y!\"\n [style.width.px]=\"\n Math.abs(distance.horizontal.x2! - distance.horizontal.x1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag\"\n [style.left.px]=\"\n (distance.horizontal.x1! + distance.horizontal.x2!) / 2\n \"\n [style.top.px]=\"distance.horizontal.y! + 10\"\n >\n {{ formatValue(distance.horizontal.value) }}\n </div>\n }\n\n @if (distance.vertical && distance.vertical.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--vertical\"\n [style.left.px]=\"distance.vertical.x!\"\n [style.top.px]=\"Math.min(distance.vertical.y1!, distance.vertical.y2!)\"\n [style.height.px]=\"\n Math.abs(distance.vertical.y2! - distance.vertical.y1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag inspector-distance-tag--vertical\"\n [style.left.px]=\"distance.vertical.x! + 10\"\n [style.top.px]=\"(distance.vertical.y1! + distance.vertical.y2!) / 2\"\n >\n {{ formatValue(distance.vertical.value) }}\n </div>\n }\n }\n\n @if (enabled()) {\n @if (toolMode() === \"guides\") {\n @if (guidePreview(); as preview) {\n <div\n class=\"inspector-guide inspector-guide--preview\"\n [class.inspector-guide--vertical]=\"preview.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"\n preview.orientation === 'horizontal'\n \"\n [style.left.px]=\"\n preview.orientation === 'vertical' ? preview.position : null\n \"\n [style.top.px]=\"\n preview.orientation === 'horizontal' ? preview.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n >\n <span class=\"inspector-guide-line\"></span>\n </div>\n }\n }\n\n @for (guide of guides(); track guide.id) {\n <button\n type=\"button\"\n class=\"inspector-guide\"\n [class.inspector-guide--vertical]=\"guide.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"guide.orientation === 'horizontal'\"\n [class.inspector-guide--selected]=\"selectedGuideId() === guide.id\"\n [style.left.px]=\"\n guide.orientation === 'vertical' ? guide.position : null\n \"\n [style.top.px]=\"\n guide.orientation === 'horizontal' ? guide.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n (pointerdown)=\"startGuideDrag($event, guide)\"\n >\n <span class=\"inspector-guide-line\"></span>\n </button>\n }\n }\n</div>\n", styles: [":host{--inspector-panel: rgba(27, 29, 36, .94);--inspector-panel-edge: rgba(255, 255, 255, .08);--inspector-text: #f3f4f6;--inspector-muted: #9ca3af;--inspector-blue: #4f8cff;--inspector-orange: #f59e0b;--inspector-green: #22c55e;--inspector-violet: #a855f7;position:fixed;inset:0;z-index:2147;pointer-events:none;font-family:SF Pro Display,Segoe UI,sans-serif}.inspector-root{position:fixed;inset:0;pointer-events:none}.inspector-toolbar{position:fixed;top:18px;right:18px;display:block;pointer-events:auto;z-index:10000}.inspector-toolbar__rail,.inspector-guide-menu{display:flex;gap:4px;padding:4px;border-radius:8px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.inspector-toolbar__rail{align-items:center}.inspector-guide-group{position:relative;display:flex;gap:0;align-items:center;border-radius:6px}.inspector-guide-menu{position:absolute;top:calc(100% + 8px);right:0;min-width:max-content;flex-direction:column;align-items:stretch}.inspector-guide-menu .inspector-segment{justify-content:flex-start;width:100%}.inspector-icon-button,.inspector-segment{display:inline-flex;align-items:center;justify-content:center;height:26px;border:0;border-radius:6px;background:transparent;color:var(--inspector-muted);cursor:pointer;transition:background .12s ease,color .12s ease;font:inherit;font-size:11px;letter-spacing:.02em}.inspector-icon-button{width:26px}.inspector-icon-button svg{width:14px;height:14px;fill:currentColor}.inspector-icon-button:hover,.inspector-segment:hover{background:#ffffff14;color:var(--inspector-text)}.inspector-icon-button.is-active,.inspector-segment.is-active{background:#4f8cff2e;color:#dbeafe}.inspector-icon-button--ghost.is-active{background:#ffffff14;color:var(--inspector-text)}.inspector-icon-button--guide-main{border-radius:6px 0 0 6px}.inspector-icon-button--guide-menu{width:20px;border-radius:0 6px 6px 0}.inspector-icon-button--guide-menu svg{width:12px;height:12px}.inspector-segment{padding:0 8px;white-space:nowrap}.inspector-segment--danger:hover{background:#ef444429;color:#fecaca}.inspector-box{position:fixed;box-sizing:border-box;pointer-events:none;border:1px solid var(--inspector-accent, var(--inspector-blue));background:color-mix(in srgb,var(--inspector-accent, var(--inspector-blue)) 8%,transparent)}.inspector-box--hover{opacity:.55}.inspector-box--selected{border-width:1.5px}.inspector-box--margin{border-color:#f59e0bd1;background:#f59e0b14}.inspector-box--padding{border-color:#22c55ed1;background:#22c55e14}.inspector-box--gap{border-color:#a855f7b8;background:#a855f70a}.inspector-box--distance{border-style:dashed;border-color:#4f8cffb3;background:transparent}.inspector-label,.inspector-box-tag,.inspector-text-chip,.inspector-distance-tag{font-variant-numeric:tabular-nums}.inspector-label{position:absolute;left:0;top:0;transform:translateY(calc(-100% - 8px));max-width:min(420px,calc(100vw - 24px));padding:6px 9px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);color:var(--inspector-text);box-shadow:0 18px 40px #00000042;font-size:11px;line-height:1;white-space:nowrap}.inspector-box-tag{position:fixed;transform:translateY(-100%);padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:var(--inspector-text);box-shadow:0 12px 24px #0003;font-size:10px;line-height:1;pointer-events:none}.inspector-box-tag--margin{color:#fbbf24}.inspector-box-tag--padding{color:#86efac;transform:translateY(0)}.inspector-box-tag--gap{color:#d8b4fe}.inspector-text-chip{position:fixed;transform:translateY(-100%);display:inline-flex;gap:6px;align-items:center;padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:#e5e7eb;box-shadow:0 12px 24px #00000038;font-size:10px;line-height:1;white-space:nowrap;pointer-events:none}.inspector-text-chip__swatch{width:8px;height:8px;border-radius:999px;border:1px solid rgba(255,255,255,.18);flex:0 0 auto}.inspector-connector{position:fixed;border-color:#4f8cffb8;border-style:dashed;pointer-events:none}.inspector-connector--vertical{border-left-width:1px;width:0}.inspector-connector--horizontal{border-top-width:1px;height:0}.inspector-distance-line{position:fixed;background:var(--inspector-blue);pointer-events:none}.inspector-distance-line--horizontal{height:1px}.inspector-distance-line--vertical{width:1px}.inspector-distance-tag{position:fixed;transform:translate(-50%);padding:5px 7px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f2;color:var(--inspector-text);font-size:10px;line-height:1;pointer-events:none}.inspector-distance-tag--vertical{transform:translateY(-50%)}.inspector-guide{position:fixed;margin:0;padding:0;border:0;background:transparent;pointer-events:auto;cursor:grab}.inspector-guide--vertical{top:0;width:14px;height:100vh;transform:translate(-50%)}.inspector-guide--horizontal{left:0;width:100vw;height:14px;transform:translateY(-50%)}.inspector-guide--preview{opacity:.55;pointer-events:none;cursor:default}.inspector-guide--preview .inspector-guide-line{background:color-mix(in srgb,var(--inspector-guide, #ff7a00) 72%,transparent)}.inspector-guide-line{position:absolute;inset:0;pointer-events:none}.inspector-guide--vertical .inspector-guide-line{left:calc(50% - .5px);width:1px;height:100%;background:var(--inspector-guide, #ff7a00)}.inspector-guide--horizontal .inspector-guide-line{top:calc(50% - .5px);width:100%;height:1px;background:var(--inspector-guide, #ff7a00)}.inspector-guide--selected .inspector-guide-line{box-shadow:0 0 0 1px #ffffff80}\n"] }]
1049
+ args: [{ selector: 'inspector-overlay', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"inspector-root\" #overlayRoot>\n @if (enabled()) {\n <div class=\"inspector-toolbar\">\n <div class=\"inspector-toolbar__rail\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'select'\"\n (click)=\"setToolMode('select')\"\n aria-label=\"Select mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2l8 8-3 .7L6.7 13 6 10 3 2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Select</span>\n </button>\n\n @if (checkpointRegistry) {\n <div class=\"inspector-checkpoint-group\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"checkpointsOpen()\"\n (click)=\"toggleCheckpoints()\"\n aria-label=\"Checkpoints\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2.5h10v11H3zm2 2v2h6v-2zm0 4v2h4v-2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Checkpoints</span>\n </button>\n\n @if (checkpointsOpen()) {\n <section class=\"inspector-checkpoint-panel\" aria-label=\"Saved checkpoints\">\n <header class=\"inspector-checkpoint-panel__header\">\n <div>\n <strong>Checkpoints</strong>\n <span>{{ checkpointRegistry.checkpoints().length }}</span>\n </div>\n <button\n type=\"button\"\n class=\"inspector-checkpoint-save\"\n (click)=\"saveCheckpoint()\"\n [disabled]=\"checkpointRegistry.isBusy()\"\n >\n {{ checkpointRegistry.isBusy() ? 'Working\u2026' : 'Save current' }}\n </button>\n </header>\n\n @if (checkpointRegistry.error(); as error) {\n <p class=\"inspector-checkpoint-message inspector-checkpoint-message--error\">{{ error }}</p>\n }\n @if (checkpointRegistry.warning(); as warning) {\n <p class=\"inspector-checkpoint-message\">{{ warning }}</p>\n }\n\n @if (checkpointRegistry.checkpoints().length) {\n <div class=\"inspector-checkpoint-list\">\n @for (checkpoint of checkpointRegistry.checkpoints(); track checkpoint.id) {\n <article class=\"inspector-checkpoint-item\">\n <div class=\"inspector-checkpoint-item__main\">\n @if (editingCheckpointId() === checkpoint.id) {\n <input\n #checkpointName\n class=\"inspector-checkpoint-name-input\"\n [value]=\"checkpoint.name\"\n (keydown.enter)=\"finishRename(checkpoint, $event)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"finishRename(checkpoint, $event)\"\n aria-label=\"Checkpoint name\"\n autofocus\n />\n } @else {\n <button type=\"button\" class=\"inspector-checkpoint-name\" (click)=\"beginRename(checkpoint)\">\n {{ checkpoint.name }}\n </button>\n }\n </div>\n <div class=\"inspector-checkpoint-actions\">\n <button type=\"button\" (click)=\"restoreCheckpoint(checkpoint)\" [disabled]=\"checkpointRegistry.isBusy()\">Restore</button>\n <button type=\"button\" (click)=\"deleteCheckpoint(checkpoint)\" [disabled]=\"checkpointRegistry.isBusy()\">Delete</button>\n </div>\n </article>\n }\n </div>\n } @else {\n <p class=\"inspector-checkpoint-empty\">No checkpoints yet.</p>\n }\n </section>\n }\n </div>\n }\n\n <div class=\"inspector-toolbar__secondary\">\n <div class=\"inspector-toolbar__reveal\">\n <div class=\"inspector-toolbar__reveal-inner\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"showTypography()\"\n (click)=\"toggleTypography()\"\n aria-label=\"Typography overlay\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 4V2h10v2h-4v10H7V4z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Typography</span>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'guides' && guideOrientation() === 'vertical'\"\n (click)=\"activateGuideOrientation('vertical')\"\n aria-label=\"Vertical guides\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M7.25 2h1.5v12h-1.5z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Vertical guides</span>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'guides' && guideOrientation() === 'horizontal'\"\n (click)=\"activateGuideOrientation('horizontal')\"\n aria-label=\"Horizontal guides\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M2 7.25h12v1.5H2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Horizontal guides</span>\n </button>\n </div>\n </div>\n </div>\n\n <span class=\"inspector-toolbar__divider\" aria-hidden=\"true\"></span>\n <button\n type=\"button\"\n class=\"inspector-icon-button inspector-icon-button--power\"\n (click)=\"disableInspector()\"\n aria-label=\"Disable inspector; press M to enable\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M7.25 1.5h1.5v6h-1.5zM4.1 3.35l.96 1.15A4.25 4.25 0 1 0 10.94 4.5l.96-1.15A5.75 5.75 0 1 1 4.1 3.35z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Disable</span>\n </button>\n </div>\n </div>\n }\n\n @if (enabled() && toolMode() === \"select\") {\n @if (hoverRect() && hoverHighlightEnabled) {\n <div\n class=\"inspector-box inspector-box--hover\"\n [style.left.px]=\"hoverRect()!.left\"\n [style.top.px]=\"hoverRect()!.top\"\n [style.width.px]=\"hoverRect()!.width\"\n [style.height.px]=\"hoverRect()!.height\"\n [style.--inspector-accent]=\"highlightColor\"\n ></div>\n }\n\n @if (selectedMeasurement(); as selected) {\n @if (selected.gap.active && selected.parentRect) {\n <div\n class=\"inspector-box inspector-box--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top\"\n [style.width.px]=\"selected.parentRect.width\"\n [style.height.px]=\"selected.parentRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top - 8\"\n >\n G {{ formatGap(selected) }}\n </div>\n }\n\n <div\n class=\"inspector-box inspector-box--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top\"\n [style.width.px]=\"selected.marginRect.width\"\n [style.height.px]=\"selected.marginRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top - 8\"\n >\n M {{ formatEdges(selected.margin) }}\n </div>\n\n <div\n class=\"inspector-box inspector-box--selected\"\n [style.left.px]=\"selected.rect.left\"\n [style.top.px]=\"selected.rect.top\"\n [style.width.px]=\"selected.rect.width\"\n [style.height.px]=\"selected.rect.height\"\n [style.--inspector-accent]=\"highlightColor\"\n >\n <div class=\"inspector-label\">{{ selectedMetaLine() }}</div>\n </div>\n\n <div\n class=\"inspector-box inspector-box--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"selected.paddingRect.top\"\n [style.width.px]=\"selected.paddingRect.width\"\n [style.height.px]=\"selected.paddingRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"\n selected.paddingRect.top + selected.paddingRect.height + 8\n \"\n >\n P {{ formatEdges(selected.padding) }}\n </div>\n }\n }\n\n @if (enabled() && showTypography()) {\n @for (block of textBlocks(); track block.id) {\n <div\n class=\"inspector-text-chip\"\n [style.left.px]=\"block.rect.left\"\n [style.top.px]=\"block.rect.top - 8\"\n >\n <span\n class=\"inspector-text-chip__swatch\"\n [style.background]=\"block.styles.color\"\n ></span>\n <span>{{ block.styles.fontSize }}</span>\n <span>{{ block.styles.lineHeight }}</span>\n <span>{{ block.styles.color }}</span>\n </div>\n }\n }\n\n @if (enabled() && altPressed() && distanceOverlay(); as distance) {\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectA.left\"\n [style.top.px]=\"distance.rectA.top\"\n [style.width.px]=\"distance.rectA.width\"\n [style.height.px]=\"distance.rectA.height\"\n ></div>\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectB.left\"\n [style.top.px]=\"distance.rectB.top\"\n [style.width.px]=\"distance.rectB.width\"\n [style.height.px]=\"distance.rectB.height\"\n ></div>\n\n @for (\n connector of distance.connectors;\n track trackConnector($index, connector)\n ) {\n <div\n class=\"inspector-connector\"\n [class.inspector-connector--vertical]=\"connector.x1 === connector.x2\"\n [class.inspector-connector--horizontal]=\"connector.y1 === connector.y2\"\n [style.left.px]=\"Math.min(connector.x1, connector.x2)\"\n [style.top.px]=\"Math.min(connector.y1, connector.y2)\"\n [style.width.px]=\"Math.max(1, Math.abs(connector.x2 - connector.x1))\"\n [style.height.px]=\"Math.max(1, Math.abs(connector.y2 - connector.y1))\"\n ></div>\n }\n\n @if (distance.horizontal && distance.horizontal.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--horizontal\"\n [style.left.px]=\"\n Math.min(distance.horizontal.x1!, distance.horizontal.x2!)\n \"\n [style.top.px]=\"distance.horizontal.y!\"\n [style.width.px]=\"\n Math.abs(distance.horizontal.x2! - distance.horizontal.x1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag\"\n [style.left.px]=\"\n (distance.horizontal.x1! + distance.horizontal.x2!) / 2\n \"\n [style.top.px]=\"distance.horizontal.y! + 10\"\n >\n {{ formatValue(distance.horizontal.value) }}\n </div>\n }\n\n @if (distance.vertical && distance.vertical.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--vertical\"\n [style.left.px]=\"distance.vertical.x!\"\n [style.top.px]=\"Math.min(distance.vertical.y1!, distance.vertical.y2!)\"\n [style.height.px]=\"\n Math.abs(distance.vertical.y2! - distance.vertical.y1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag inspector-distance-tag--vertical\"\n [style.left.px]=\"distance.vertical.x! + 10\"\n [style.top.px]=\"(distance.vertical.y1! + distance.vertical.y2!) / 2\"\n >\n {{ formatValue(distance.vertical.value) }}\n </div>\n }\n }\n\n @if (enabled()) {\n @if (toolMode() === \"guides\") {\n @if (guidePreview(); as preview) {\n <div\n class=\"inspector-guide inspector-guide--preview\"\n [class.inspector-guide--vertical]=\"preview.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"\n preview.orientation === 'horizontal'\n \"\n [style.left.px]=\"\n preview.orientation === 'vertical' ? preview.position : null\n \"\n [style.top.px]=\"\n preview.orientation === 'horizontal' ? preview.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n >\n <span class=\"inspector-guide-line\"></span>\n </div>\n }\n }\n\n @for (guide of guides(); track guide.id) {\n <button\n type=\"button\"\n class=\"inspector-guide\"\n [class.inspector-guide--vertical]=\"guide.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"guide.orientation === 'horizontal'\"\n [class.inspector-guide--selected]=\"selectedGuideId() === guide.id\"\n [style.left.px]=\"\n guide.orientation === 'vertical' ? guide.position : null\n \"\n [style.top.px]=\"\n guide.orientation === 'horizontal' ? guide.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n (pointerdown)=\"startGuideDrag($event, guide)\"\n >\n <span class=\"inspector-guide-line\"></span>\n </button>\n }\n }\n</div>\n", styles: [":host{--inspector-panel: rgba(27, 29, 36, .94);--inspector-panel-edge: rgba(255, 255, 255, .08);--inspector-text: #f3f4f6;--inspector-muted: #9ca3af;--inspector-blue: #4f8cff;--inspector-orange: #f59e0b;--inspector-green: #22c55e;--inspector-violet: #a855f7;position:fixed;inset:0;z-index:2147;pointer-events:none;font-family:SF Pro Display,Segoe UI,sans-serif}.inspector-root{position:fixed;inset:0;pointer-events:none}.inspector-toolbar{position:fixed;top:12px;right:12px;display:block;pointer-events:auto;z-index:10000}.inspector-toolbar__rail{display:flex;flex-direction:column;align-items:center;gap:2px;width:30px;padding:2px;border-radius:4px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.inspector-toolbar__divider{width:16px;height:1px;margin-block:2px;background:var(--inspector-panel-edge)}.inspector-toolbar__secondary{display:grid;width:24px}.inspector-toolbar__reveal{display:grid;grid-template-rows:0fr;opacity:0;transform:translateY(-4px);transition:grid-template-rows .18s cubic-bezier(.16,1,.3,1),opacity .12s cubic-bezier(.16,1,.3,1),transform .18s cubic-bezier(.16,1,.3,1)}.inspector-toolbar__reveal-inner{display:grid;gap:2px;min-height:0;overflow:hidden}.inspector-toolbar__rail:hover .inspector-toolbar__reveal,.inspector-toolbar__rail:focus-within .inspector-toolbar__reveal{grid-template-rows:1fr;opacity:1;transform:translateY(2px)}.inspector-checkpoint-group{position:relative;width:24px;height:24px}.inspector-checkpoint-panel{position:absolute;top:0;right:calc(100% + 8px);width:min(300px,calc(100vw - 24px));max-height:min(420px,calc(100vh - 64px));overflow:auto;border:1px solid var(--inspector-panel-edge);border-radius:8px;background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;color:var(--inspector-text)}.inspector-checkpoint-panel__header,.inspector-checkpoint-item,.inspector-checkpoint-actions{display:flex;align-items:center}.inspector-checkpoint-panel__header{justify-content:space-between;gap:8px;padding:6px 8px;border-bottom:1px solid var(--inspector-panel-edge)}.inspector-checkpoint-panel__header strong,.inspector-checkpoint-panel__header span{display:inline}.inspector-checkpoint-panel__header strong{font-size:12px}.inspector-checkpoint-panel__header span{margin-left:5px}.inspector-checkpoint-panel__header span,.inspector-checkpoint-empty,.inspector-checkpoint-message{color:var(--inspector-muted);font-size:10px;line-height:1.4}.inspector-checkpoint-save,.inspector-checkpoint-actions button,.inspector-checkpoint-name{border:0;background:transparent;color:inherit;cursor:pointer;font:inherit}.inspector-checkpoint-save{padding:5px 7px;border-radius:5px;background:#4f8cff2e;color:#dbeafe;font-size:10px}.inspector-checkpoint-list{display:grid}.inspector-checkpoint-item{justify-content:space-between;gap:8px;padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.05)}.inspector-checkpoint-item__main{flex:1 1 auto;min-width:0}.inspector-checkpoint-name,.inspector-checkpoint-name-input{display:block;max-width:100%;overflow:hidden;padding:0;color:var(--inspector-text);font-size:11px;font-weight:600;text-align:left;text-overflow:ellipsis;white-space:nowrap}.inspector-checkpoint-name-input{width:100%;border:1px solid rgba(79,140,255,.6);border-radius:3px;background:#0003;outline:none}.inspector-checkpoint-actions{gap:2px;flex:0 0 auto}.inspector-checkpoint-actions button{padding:4px;border-radius:4px;color:var(--inspector-muted);font-size:10px}.inspector-checkpoint-actions button:hover,.inspector-checkpoint-name:hover,.inspector-checkpoint-save:hover{color:var(--inspector-text);background:#ffffff14}.inspector-checkpoint-empty,.inspector-checkpoint-message{margin:0;padding:12px 10px}.inspector-checkpoint-message--error{color:#fecaca}.inspector-icon-button{display:inline-flex;align-items:center;justify-content:center;height:24px;border:0;border-radius:3px;background:transparent;color:var(--inspector-muted);cursor:pointer;transition:background .1s cubic-bezier(.16,1,.3,1),color .1s cubic-bezier(.16,1,.3,1),transform .1s cubic-bezier(.16,1,.3,1);font:inherit;font-size:11px;letter-spacing:.02em}.inspector-icon-button{position:relative;width:24px}.inspector-icon-button svg{width:13px;height:13px;fill:currentColor}.inspector-icon-button:active{transform:translateY(1px)}.inspector-icon-button:focus-visible{outline:2px solid var(--inspector-blue);outline-offset:2px}.inspector-icon-button:hover{background:#ffffff14;color:var(--inspector-text)}.inspector-icon-button.is-active{background:#4f8cff2e;color:#dbeafe}.inspector-icon-button--power:hover{background:#ef444429;color:#fecaca}.inspector-tooltip{position:absolute;top:50%;right:calc(100% + 7px);z-index:20;width:max-content;padding:4px 6px;border:1px solid var(--inspector-panel-edge);border-radius:3px;background:#111318fa;color:var(--inspector-text);font-size:10px;font-weight:600;line-height:1.2;opacity:0;pointer-events:none;transform:translate(3px,-50%);transition:opacity .12s cubic-bezier(.16,1,.3,1),transform .12s cubic-bezier(.16,1,.3,1);white-space:nowrap}.inspector-icon-button:hover .inspector-tooltip,.inspector-icon-button:focus-visible .inspector-tooltip{opacity:1;transform:translateY(-50%)}@media (prefers-reduced-motion: reduce){.inspector-toolbar__reveal,.inspector-icon-button,.inspector-tooltip{transition-duration:.01ms}}.inspector-box{position:fixed;box-sizing:border-box;pointer-events:none;border:1px solid var(--inspector-accent, var(--inspector-blue));background:color-mix(in srgb,var(--inspector-accent, var(--inspector-blue)) 8%,transparent)}.inspector-box--hover{opacity:.55}.inspector-box--selected{border-width:1.5px}.inspector-box--margin{border-color:#f59e0bd1;background:#f59e0b14}.inspector-box--padding{border-color:#22c55ed1;background:#22c55e14}.inspector-box--gap{border-color:#a855f7b8;background:#a855f70a}.inspector-box--distance{border-style:dashed;border-color:#4f8cffb3;background:transparent}.inspector-label,.inspector-box-tag,.inspector-text-chip,.inspector-distance-tag{font-variant-numeric:tabular-nums}.inspector-label{position:absolute;left:0;top:0;transform:translateY(calc(-100% - 8px));max-width:min(420px,calc(100vw - 24px));padding:6px 9px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);color:var(--inspector-text);box-shadow:0 18px 40px #00000042;font-size:11px;line-height:1;white-space:nowrap}.inspector-box-tag{position:fixed;transform:translateY(-100%);padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:var(--inspector-text);box-shadow:0 12px 24px #0003;font-size:10px;line-height:1;pointer-events:none}.inspector-box-tag--margin{color:#fbbf24}.inspector-box-tag--padding{color:#86efac;transform:translateY(0)}.inspector-box-tag--gap{color:#d8b4fe}.inspector-text-chip{position:fixed;transform:translateY(-100%);display:inline-flex;gap:6px;align-items:center;padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:#e5e7eb;box-shadow:0 12px 24px #00000038;font-size:10px;line-height:1;white-space:nowrap;pointer-events:none}.inspector-text-chip__swatch{width:8px;height:8px;border-radius:999px;border:1px solid rgba(255,255,255,.18);flex:0 0 auto}.inspector-connector{position:fixed;border-color:#4f8cffb8;border-style:dashed;pointer-events:none}.inspector-connector--vertical{border-left-width:1px;width:0}.inspector-connector--horizontal{border-top-width:1px;height:0}.inspector-distance-line{position:fixed;background:var(--inspector-blue);pointer-events:none}.inspector-distance-line--horizontal{height:1px}.inspector-distance-line--vertical{width:1px}.inspector-distance-tag{position:fixed;transform:translate(-50%);padding:5px 7px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f2;color:var(--inspector-text);font-size:10px;line-height:1;pointer-events:none}.inspector-distance-tag--vertical{transform:translateY(-50%)}.inspector-guide{position:fixed;margin:0;padding:0;border:0;background:transparent;pointer-events:auto;cursor:grab}.inspector-guide--vertical{top:0;width:14px;height:100vh;transform:translate(-50%)}.inspector-guide--horizontal{left:0;width:100vw;height:14px;transform:translateY(-50%)}.inspector-guide--preview{opacity:.55;pointer-events:none;cursor:default}.inspector-guide--preview .inspector-guide-line{background:color-mix(in srgb,var(--inspector-guide, #ff7a00) 72%,transparent)}.inspector-guide-line{position:absolute;inset:0;pointer-events:none}.inspector-guide--vertical .inspector-guide-line{left:calc(50% - .5px);width:1px;height:100%;background:var(--inspector-guide, #ff7a00)}.inspector-guide--horizontal .inspector-guide-line{top:calc(50% - .5px);width:100%;height:1px;background:var(--inspector-guide, #ff7a00)}.inspector-guide--selected .inspector-guide-line{box-shadow:0 0 0 1px #ffffff80}\n"] }]
703
1050
  }], ctorParameters: () => [{ type: undefined, decorators: [{
704
1051
  type: Inject,
705
1052
  args: [PLATFORM_ID]
@@ -713,30 +1060,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
713
1060
  type: Input
714
1061
  }], handleKeydown: [{
715
1062
  type: HostListener,
716
- args: ["window:keydown", ["$event"]]
1063
+ args: ['window:keydown', ['$event']]
717
1064
  }], handleKeyup: [{
718
1065
  type: HostListener,
719
- args: ["window:keyup", ["$event"]]
1066
+ args: ['window:keyup', ['$event']]
720
1067
  }], handlePointerMove: [{
721
1068
  type: HostListener,
722
- args: ["window:pointermove", ["$event"]]
1069
+ args: ['window:pointermove', ['$event']]
723
1070
  }], handlePointerUp: [{
724
1071
  type: HostListener,
725
- args: ["window:pointerup"]
1072
+ args: ['window:pointerup']
726
1073
  }], handleClick: [{
727
1074
  type: HostListener,
728
- args: ["window:click", ["$event"]]
1075
+ args: ['window:click', ['$event']]
729
1076
  }], handleViewportChange: [{
730
1077
  type: HostListener,
731
- args: ["window:resize"]
1078
+ args: ['window:resize']
732
1079
  }, {
733
1080
  type: HostListener,
734
- args: ["window:scroll"]
1081
+ args: ['window:scroll']
735
1082
  }] } });
736
1083
 
737
1084
  /**
738
1085
  * Generated bundle index. Do not edit.
739
1086
  */
740
1087
 
741
- export { GUIDE_HITBOX_SIZE, GUIDE_SNAP_DISTANCE, clamp, createId, formatValue, getDistanceOverlay, getElementLabel, getInspectMeasurement, getRectFromDom, getTargetElement, getTextInspection, getViewportSize, getVisibleTextBlocks, inspectorComponent, inspector_STATE_VERSION, inspector_STORAGE_KEY, rectContainsPoint, rectsEqual };
1088
+ export { GUIDE_HITBOX_SIZE, GUIDE_SNAP_DISTANCE, InspectorCheckpointRegistry, clamp, createBehaviorSubjectCheckpointAdapter, createId, formatValue, getDistanceOverlay, getElementLabel, getInspectMeasurement, getRectFromDom, getTargetElement, getTextInspection, getViewportSize, getVisibleTextBlocks, inspectorComponent, inspector_STATE_VERSION, inspector_STORAGE_KEY, provideInspectorCheckpoints, rectContainsPoint, rectsEqual };
742
1089
  //# sourceMappingURL=inspector-ng.mjs.map