inspector-ng 0.0.6 → 0.0.9

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,332 @@
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 { InjectionToken, PLATFORM_ID, Inject, Injectable, signal, Optional, makeEnvironmentProviders, viewChild, inject, computed, effect, HostListener, Input, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { isPlatformBrowser } from '@angular/common';
4
+ import * as i2 from '@angular/router';
5
+ import { Router } from '@angular/router';
6
+ import * as i1 from '@ngrx/store';
7
+ import { Store, META_REDUCERS } from '@ngrx/store';
8
+ import { firstValueFrom, take } from 'rxjs';
4
9
 
5
10
  const inspector_STORAGE_KEY = "inspector-state";
6
- const inspector_STATE_VERSION = 1;
11
+ const inspector_STATE_VERSION = 2;
7
12
  const GUIDE_SNAP_DISTANCE = 10;
8
13
  const GUIDE_HITBOX_SIZE = 14;
9
14
 
15
+ const RESTORE_CHECKPOINT = '[Inspector Checkpoints] Restore';
16
+ const DATABASE_NAME = 'inspector-ng';
17
+ const DATABASE_VERSION = 1;
18
+ const STORE_NAME = 'checkpoints';
19
+ const INSPECTOR_CHECKPOINT_REPOSITORY = new InjectionToken('INSPECTOR_CHECKPOINT_REPOSITORY');
20
+ function inspectorCheckpointMetaReducer(reducer) {
21
+ return (state, action) => {
22
+ if (action.type === RESTORE_CHECKPOINT) {
23
+ return action.state;
24
+ }
25
+ return reducer(state, action);
26
+ };
27
+ }
28
+ class InspectorCheckpointRepository {
29
+ }
30
+ class IndexedDbCheckpointRepository extends InspectorCheckpointRepository {
31
+ constructor(platformId) {
32
+ super();
33
+ this.platformId = platformId;
34
+ this.databasePromise = null;
35
+ }
36
+ async list() {
37
+ const database = await this.database();
38
+ const values = await this.request(database.transaction(STORE_NAME, 'readonly').objectStore(STORE_NAME).getAll());
39
+ return values
40
+ .filter(isCheckpointRecord)
41
+ .sort(newestFirst$1);
42
+ }
43
+ async put(checkpoint) {
44
+ const database = await this.database();
45
+ await this.transactionComplete(database, 'readwrite', (store) => store.put(checkpoint));
46
+ }
47
+ async delete(id) {
48
+ const database = await this.database();
49
+ await this.transactionComplete(database, 'readwrite', (store) => store.delete(id));
50
+ }
51
+ database() {
52
+ if (!isPlatformBrowser(this.platformId) || typeof indexedDB === 'undefined') {
53
+ return Promise.reject(new Error('Checkpoints are only available in a browser with IndexedDB.'));
54
+ }
55
+ if (this.databasePromise)
56
+ return this.databasePromise;
57
+ this.databasePromise = new Promise((resolve, reject) => {
58
+ const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION);
59
+ request.onupgradeneeded = () => {
60
+ const database = request.result;
61
+ if (database.objectStoreNames.contains(STORE_NAME))
62
+ return;
63
+ const store = database.createObjectStore(STORE_NAME, { keyPath: 'id' });
64
+ store.createIndex('createdAt', 'createdAt');
65
+ };
66
+ request.onsuccess = () => resolve(request.result);
67
+ request.onerror = () => reject(request.error ?? new Error('IndexedDB could not be opened.'));
68
+ request.onblocked = () => reject(new Error('IndexedDB is blocked by another open tab.'));
69
+ });
70
+ return this.databasePromise;
71
+ }
72
+ request(request) {
73
+ return new Promise((resolve, reject) => {
74
+ request.onsuccess = () => resolve(request.result);
75
+ request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed.'));
76
+ });
77
+ }
78
+ transactionComplete(database, mode, operation) {
79
+ return new Promise((resolve, reject) => {
80
+ const transaction = database.transaction(STORE_NAME, mode);
81
+ transaction.oncomplete = () => resolve();
82
+ transaction.onerror = () => reject(transaction.error ?? new Error('IndexedDB transaction failed.'));
83
+ transaction.onabort = () => reject(transaction.error ?? new Error('IndexedDB transaction was aborted.'));
84
+ operation(transaction.objectStore(STORE_NAME));
85
+ });
86
+ }
87
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: IndexedDbCheckpointRepository, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
88
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: IndexedDbCheckpointRepository }); }
89
+ }
90
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: IndexedDbCheckpointRepository, decorators: [{
91
+ type: Injectable
92
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
93
+ type: Inject,
94
+ args: [PLATFORM_ID]
95
+ }] }] });
96
+ class InspectorCheckpointService {
97
+ constructor(repository, store, router, platformId) {
98
+ this.repository = repository;
99
+ this.store = store;
100
+ this.router = router;
101
+ this.platformId = platformId;
102
+ this.checkpoints = signal([]);
103
+ this.loading = signal(false);
104
+ this.busy = signal(false);
105
+ this.error = signal(null);
106
+ }
107
+ async load() {
108
+ this.error.set(null);
109
+ this.loading.set(true);
110
+ try {
111
+ this.checkpoints.set((await this.repository.list()).sort(newestFirst$1));
112
+ }
113
+ catch (error) {
114
+ this.error.set(this.messageFor(error, 'Saved checkpoints could not be loaded. Try reloading the page.'));
115
+ }
116
+ finally {
117
+ this.loading.set(false);
118
+ }
119
+ }
120
+ async save() {
121
+ this.error.set(null);
122
+ if (!this.store) {
123
+ this.error.set(this.missingStoreMessage());
124
+ return null;
125
+ }
126
+ this.busy.set(true);
127
+ try {
128
+ const currentState = await firstValueFrom(this.store.pipe(take(1)));
129
+ const state = cloneJson(currentState);
130
+ const persisted = await this.repository.list();
131
+ const route = this.currentRoute();
132
+ const pathname = this.currentPathname();
133
+ const checkpoint = {
134
+ version: 1,
135
+ id: createCheckpointId(),
136
+ name: nextAutomaticName(pathname, persisted.map(({ name }) => name)),
137
+ route,
138
+ createdAt: new Date().toISOString(),
139
+ state,
140
+ };
141
+ await this.repository.put(checkpoint);
142
+ this.checkpoints.set([checkpoint, ...persisted].sort(newestFirst$1));
143
+ return checkpoint;
144
+ }
145
+ catch (error) {
146
+ this.error.set(this.storageMessage(error, 'The checkpoint could not be saved.'));
147
+ return null;
148
+ }
149
+ finally {
150
+ this.busy.set(false);
151
+ }
152
+ }
153
+ async restore(id) {
154
+ this.error.set(null);
155
+ if (!this.store) {
156
+ this.error.set(this.missingStoreMessage());
157
+ return false;
158
+ }
159
+ this.busy.set(true);
160
+ try {
161
+ const checkpoint = (await this.repository.list()).find((item) => item.id === id);
162
+ if (!checkpoint)
163
+ throw new Error('Checkpoint not found. It may have been deleted in another tab.');
164
+ const state = cloneJson(checkpoint.state, `“${checkpoint.name}” could not be decoded. Delete it and save a new checkpoint.`);
165
+ this.store.dispatch({ type: RESTORE_CHECKPOINT, state });
166
+ if (this.router && !await this.router.navigateByUrl(checkpoint.route)) {
167
+ throw new Error(`The state was restored, but navigation to “${checkpoint.route}” was cancelled.`);
168
+ }
169
+ return true;
170
+ }
171
+ catch (error) {
172
+ this.error.set(this.messageFor(error, 'The checkpoint could not be restored. Delete it and save a new one.'));
173
+ return false;
174
+ }
175
+ finally {
176
+ this.busy.set(false);
177
+ }
178
+ }
179
+ async rename(id, name) {
180
+ this.error.set(null);
181
+ const nextName = name.trim();
182
+ if (!nextName) {
183
+ this.error.set('Please enter a checkpoint name.');
184
+ return false;
185
+ }
186
+ this.busy.set(true);
187
+ try {
188
+ const checkpoints = await this.repository.list();
189
+ if (checkpoints.some((item) => item.id !== id && item.name.toLocaleLowerCase() === nextName.toLocaleLowerCase())) {
190
+ throw new Error(`A checkpoint named “${nextName}” already exists. Choose a different name.`);
191
+ }
192
+ const checkpoint = checkpoints.find((item) => item.id === id);
193
+ if (!checkpoint)
194
+ throw new Error('Checkpoint not found. It may have been deleted in another tab.');
195
+ await this.repository.put({ ...checkpoint, name: nextName });
196
+ this.checkpoints.set(checkpoints.map((item) => item.id === id ? { ...item, name: nextName } : item).sort(newestFirst$1));
197
+ return true;
198
+ }
199
+ catch (error) {
200
+ this.error.set(this.storageMessage(error, 'The checkpoint could not be renamed.'));
201
+ return false;
202
+ }
203
+ finally {
204
+ this.busy.set(false);
205
+ }
206
+ }
207
+ async delete(id) {
208
+ this.error.set(null);
209
+ this.busy.set(true);
210
+ try {
211
+ await this.repository.delete(id);
212
+ this.checkpoints.set(this.checkpoints().filter((item) => item.id !== id));
213
+ return true;
214
+ }
215
+ catch (error) {
216
+ this.error.set(this.storageMessage(error, 'The checkpoint could not be deleted.'));
217
+ return false;
218
+ }
219
+ finally {
220
+ this.busy.set(false);
221
+ }
222
+ }
223
+ clearError() {
224
+ this.error.set(null);
225
+ }
226
+ currentRoute() {
227
+ if (!isPlatformBrowser(this.platformId))
228
+ return '/';
229
+ return `${this.currentPathname()}${window.location.search}${window.location.hash}`;
230
+ }
231
+ currentPathname() {
232
+ if (!isPlatformBrowser(this.platformId))
233
+ return '/';
234
+ return window.location.pathname || '/';
235
+ }
236
+ missingStoreMessage() {
237
+ return 'The NgRx root store is unavailable. Check that provideInspectorCheckpoints() is registered after provideStore().';
238
+ }
239
+ storageMessage(error, fallback) {
240
+ if (isQuotaError(error)) {
241
+ return 'Browser storage is full. Delete old checkpoints, then try saving again.';
242
+ }
243
+ return this.messageFor(error, fallback);
244
+ }
245
+ messageFor(error, fallback) {
246
+ return error instanceof Error && error.message ? error.message : fallback;
247
+ }
248
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: InspectorCheckpointService, deps: [{ token: INSPECTOR_CHECKPOINT_REPOSITORY }, { token: Store, optional: true }, { token: Router, optional: true }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
249
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: InspectorCheckpointService }); }
250
+ }
251
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: InspectorCheckpointService, decorators: [{
252
+ type: Injectable
253
+ }], ctorParameters: () => [{ type: InspectorCheckpointRepository, decorators: [{
254
+ type: Inject,
255
+ args: [INSPECTOR_CHECKPOINT_REPOSITORY]
256
+ }] }, { type: i1.Store, decorators: [{
257
+ type: Optional
258
+ }, {
259
+ type: Inject,
260
+ args: [Store]
261
+ }] }, { type: i2.Router, decorators: [{
262
+ type: Optional
263
+ }, {
264
+ type: Inject,
265
+ args: [Router]
266
+ }] }, { type: undefined, decorators: [{
267
+ type: Inject,
268
+ args: [PLATFORM_ID]
269
+ }] }] });
270
+ function provideInspectorCheckpoints() {
271
+ return makeEnvironmentProviders([
272
+ IndexedDbCheckpointRepository,
273
+ {
274
+ provide: INSPECTOR_CHECKPOINT_REPOSITORY,
275
+ useExisting: IndexedDbCheckpointRepository,
276
+ },
277
+ InspectorCheckpointService,
278
+ {
279
+ provide: META_REDUCERS,
280
+ multi: true,
281
+ useValue: inspectorCheckpointMetaReducer,
282
+ },
283
+ ]);
284
+ }
285
+ function nextAutomaticName(route, names) {
286
+ const used = new Set(names.map((name) => name.toLocaleLowerCase()));
287
+ if (!used.has(route.toLocaleLowerCase()))
288
+ return route;
289
+ for (let suffix = 2;; suffix += 1) {
290
+ const candidate = `${route} ${suffix}`;
291
+ if (!used.has(candidate.toLocaleLowerCase()))
292
+ return candidate;
293
+ }
294
+ }
295
+ function cloneJson(value, failureMessage = 'The NgRx root state is not JSON-serializable. Remove circular or unsupported values and try again.') {
296
+ try {
297
+ const serialized = JSON.stringify(value);
298
+ if (serialized === undefined)
299
+ throw new Error();
300
+ return JSON.parse(serialized);
301
+ }
302
+ catch {
303
+ throw new Error(failureMessage);
304
+ }
305
+ }
306
+ function createCheckpointId() {
307
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
308
+ return crypto.randomUUID();
309
+ }
310
+ return `checkpoint-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
311
+ }
312
+ function isQuotaError(error) {
313
+ return error instanceof DOMException && (error.name === 'QuotaExceededError' || error.name === 'NS_ERROR_DOM_QUOTA_REACHED');
314
+ }
315
+ function newestFirst$1(a, b) {
316
+ return b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id);
317
+ }
318
+ function isCheckpointRecord(value) {
319
+ if (!value || typeof value !== 'object')
320
+ return false;
321
+ const checkpoint = value;
322
+ return checkpoint.version === 1 &&
323
+ typeof checkpoint.id === 'string' &&
324
+ typeof checkpoint.name === 'string' &&
325
+ typeof checkpoint.route === 'string' &&
326
+ typeof checkpoint.createdAt === 'string' &&
327
+ 'state' in checkpoint;
328
+ }
329
+
10
330
  const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
11
331
  const getViewportSize = () => ({
12
332
  width: typeof window === 'undefined' ? 1 : window.innerWidth || 1,
@@ -267,6 +587,56 @@ const getTargetElement = (point, overlayElement) => {
267
587
  return document.elementFromPoint(point.x, point.y);
268
588
  };
269
589
 
590
+ function fuzzyCheckpoints(checkpoints, query) {
591
+ const normalizedQuery = normalize(query.trim());
592
+ if (!normalizedQuery)
593
+ return [...checkpoints].sort(newestFirst);
594
+ return checkpoints
595
+ .map((checkpoint) => {
596
+ const score = fuzzyScore(checkpoint.name, normalizedQuery);
597
+ return score === null ? null : { checkpoint, score };
598
+ })
599
+ .filter((item) => item !== null)
600
+ .sort((a, b) => b.score - a.score || newestFirst(a.checkpoint, b.checkpoint))
601
+ .map(({ checkpoint }) => checkpoint);
602
+ }
603
+ function fuzzyScore(value, normalizedQuery) {
604
+ const candidate = normalize(value);
605
+ if (candidate === normalizedQuery)
606
+ return 10_000;
607
+ if (candidate.startsWith(normalizedQuery))
608
+ return 8_000 - candidate.length;
609
+ const substringIndex = candidate.indexOf(normalizedQuery);
610
+ if (substringIndex >= 0)
611
+ return 6_000 - substringIndex * 10 - candidate.length;
612
+ let queryIndex = 0;
613
+ let firstIndex = -1;
614
+ let lastIndex = -1;
615
+ let gapPenalty = 0;
616
+ for (let index = 0; index < candidate.length && queryIndex < normalizedQuery.length; index += 1) {
617
+ if (candidate[index] !== normalizedQuery[queryIndex])
618
+ continue;
619
+ if (firstIndex < 0)
620
+ firstIndex = index;
621
+ if (lastIndex >= 0)
622
+ gapPenalty += index - lastIndex - 1;
623
+ lastIndex = index;
624
+ queryIndex += 1;
625
+ }
626
+ if (queryIndex !== normalizedQuery.length)
627
+ return null;
628
+ return 4_000 - firstIndex * 10 - gapPenalty * 20 - candidate.length;
629
+ }
630
+ function normalize(value) {
631
+ return value
632
+ .normalize('NFKD')
633
+ .replace(/[\u0300-\u036f]/g, '')
634
+ .toLocaleLowerCase();
635
+ }
636
+ function newestFirst(a, b) {
637
+ return b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id);
638
+ }
639
+
270
640
  const createId = () => typeof crypto !== 'undefined' && 'randomUUID' in crypto
271
641
  ? crypto.randomUUID()
272
642
  : `${Date.now()}-${Math.random()}`;
@@ -278,22 +648,33 @@ class inspectorComponent {
278
648
  this.formatValue = formatValue;
279
649
  this.guideHitboxSize = GUIDE_HITBOX_SIZE;
280
650
  this.overlayRoot = viewChild('overlayRoot');
651
+ this.checkpointDialog = viewChild('checkpointDialog');
652
+ this.checkpointSearch = viewChild('checkpointSearch');
281
653
  this.highlightColor = '#4f8cff';
282
654
  this.guideColor = '#ff7a00';
283
655
  this.hoverHighlightEnabled = true;
284
656
  this.persistOnReload = false;
285
- this.enabled = signal(false);
657
+ this.checkpointService = inject(InspectorCheckpointService, { optional: true });
658
+ this.commandBarOpen = signal(false);
659
+ this.checkpointQuery = signal('');
660
+ this.activeCheckpointIndex = signal(0);
661
+ this.editingCheckpointId = signal(null);
662
+ this.deletingCheckpointId = signal(null);
663
+ this.renameDraft = signal('');
664
+ this.checkpointToast = signal(null);
665
+ this.checkpointToastIsError = signal(false);
666
+ this.filteredCheckpoints = computed(() => fuzzyCheckpoints(this.checkpointService?.checkpoints() ?? [], this.checkpointQuery()));
667
+ this.enabled = signal(true);
286
668
  this.toolMode = signal('none');
287
669
  this.guideOrientation = signal('vertical');
288
670
  this.guides = signal([]);
289
671
  this.hoverRect = signal(null);
290
672
  this.selectedGuideId = signal(null);
291
673
  this.selectedMeasurement = signal(null);
292
- this.showTypography = signal(true);
674
+ this.showTypography = signal(false);
293
675
  this.altPressed = signal(false);
294
676
  this.distanceOverlay = signal(null);
295
677
  this.textBlocks = signal([]);
296
- this.guideMenuOpen = signal(false);
297
678
  this.guidePreview = signal(null);
298
679
  this.selectedMetaLine = computed(() => {
299
680
  const selected = this.selectedMeasurement();
@@ -318,6 +699,7 @@ class inspectorComponent {
318
699
  this.draggingGuideId = null;
319
700
  this.selectedElement = null;
320
701
  this.hoverElement = null;
702
+ this.toastTimer = null;
321
703
  this.canUndo = computed(() => this.historyIndex() > 0);
322
704
  this.canRedo = computed(() => this.historyIndex() >= 0 &&
323
705
  this.historyIndex() < this.history().length - 1);
@@ -359,12 +741,12 @@ class inspectorComponent {
359
741
  }
360
742
  try {
361
743
  const parsed = JSON.parse(stored);
362
- if (parsed.version === inspector_STATE_VERSION) {
363
- this.enabled.set(parsed.enabled);
744
+ if (parsed.version === inspector_STATE_VERSION || parsed.version === 1) {
745
+ this.enabled.set(true);
364
746
  this.toolMode.set(parsed.toolMode);
365
747
  this.guideOrientation.set(parsed.guideOrientation);
366
748
  this.guides.set(parsed.guides ?? []);
367
- this.showTypography.set(parsed.showTypography ?? true);
749
+ this.showTypography.set(parsed.version === 1 ? false : (parsed.showTypography ?? false));
368
750
  this.recordHistory(parsed.guides ?? []);
369
751
  }
370
752
  }
@@ -381,6 +763,8 @@ class inspectorComponent {
381
763
  window.removeEventListener('click', this.handleCapturedClick, {
382
764
  capture: true,
383
765
  });
766
+ if (this.toastTimer)
767
+ clearTimeout(this.toastTimer);
384
768
  }
385
769
  setToolMode(mode) {
386
770
  if (!this.enabled()) {
@@ -396,12 +780,125 @@ class inspectorComponent {
396
780
  this.guideOrientation.set(orientation);
397
781
  this.guidePreview.set(null);
398
782
  }
783
+ activateGuideOrientation(orientation) {
784
+ const isActive = this.toolMode() === 'guides' && this.guideOrientation() === orientation;
785
+ this.setGuideOrientation(orientation);
786
+ if (isActive || this.toolMode() !== 'guides') {
787
+ this.setToolMode('guides');
788
+ }
789
+ }
399
790
  toggleTypography() {
400
791
  this.showTypography.update((value) => !value);
401
792
  this.refreshTypographyBlocks();
402
793
  }
403
- toggleGuideMenu() {
404
- this.guideMenuOpen.update((value) => !value);
794
+ disableInspector() {
795
+ this.setInspectorEnabled(false);
796
+ }
797
+ async saveCheckpoint() {
798
+ const checkpoint = await this.checkpointService?.save();
799
+ if (checkpoint) {
800
+ this.showCheckpointToast(`Saved “${checkpoint.name}”.`);
801
+ }
802
+ else if (this.checkpointService?.error()) {
803
+ this.showCheckpointToast(this.checkpointService.error(), true);
804
+ }
805
+ }
806
+ async openCheckpointCommandBar() {
807
+ if (!this.checkpointService || !this.isBrowser || this.commandBarOpen())
808
+ return;
809
+ this.commandBarOpen.set(true);
810
+ this.checkpointQuery.set('');
811
+ this.activeCheckpointIndex.set(0);
812
+ this.editingCheckpointId.set(null);
813
+ this.deletingCheckpointId.set(null);
814
+ this.checkpointService.clearError();
815
+ this.checkpointDialog()?.nativeElement.showModal();
816
+ queueMicrotask(() => this.checkpointSearch()?.nativeElement.focus());
817
+ await this.checkpointService.load();
818
+ this.clampActiveCheckpoint();
819
+ }
820
+ closeCheckpointCommandBar() {
821
+ if (this.checkpointService?.busy())
822
+ return;
823
+ this.checkpointDialog()?.nativeElement.close();
824
+ this.commandBarOpen.set(false);
825
+ this.editingCheckpointId.set(null);
826
+ this.deletingCheckpointId.set(null);
827
+ }
828
+ updateCheckpointQuery(event) {
829
+ this.checkpointQuery.set(event.target.value);
830
+ this.activeCheckpointIndex.set(0);
831
+ this.editingCheckpointId.set(null);
832
+ this.deletingCheckpointId.set(null);
833
+ this.checkpointService?.clearError();
834
+ }
835
+ setActiveCheckpoint(index) {
836
+ this.activeCheckpointIndex.set(index);
837
+ }
838
+ async restoreCheckpoint(checkpoint) {
839
+ if (!this.checkpointService || this.checkpointService.busy())
840
+ return;
841
+ const restored = await this.checkpointService.restore(checkpoint.id);
842
+ if (!restored)
843
+ return;
844
+ this.checkpointDialog()?.nativeElement.close();
845
+ this.commandBarOpen.set(false);
846
+ this.showCheckpointToast(`Restored “${checkpoint.name}”.`);
847
+ }
848
+ beginRename(checkpoint, event) {
849
+ event?.stopPropagation();
850
+ if (this.checkpointService?.busy())
851
+ return;
852
+ this.editingCheckpointId.set(checkpoint.id);
853
+ this.deletingCheckpointId.set(null);
854
+ this.renameDraft.set(checkpoint.name);
855
+ queueMicrotask(() => {
856
+ const input = this.checkpointDialog()?.nativeElement.querySelector('.inspector-command-row__rename');
857
+ input?.focus();
858
+ input?.select();
859
+ });
860
+ }
861
+ updateRenameDraft(event) {
862
+ this.renameDraft.set(event.target.value);
863
+ this.checkpointService?.clearError();
864
+ }
865
+ async finishRename(checkpoint) {
866
+ if (await this.checkpointService?.rename(checkpoint.id, this.renameDraft())) {
867
+ this.editingCheckpointId.set(null);
868
+ this.checkpointSearch()?.nativeElement.focus();
869
+ }
870
+ }
871
+ cancelRename() {
872
+ this.editingCheckpointId.set(null);
873
+ this.checkpointService?.clearError();
874
+ this.checkpointSearch()?.nativeElement.focus();
875
+ }
876
+ beginDelete(checkpoint, event) {
877
+ event?.stopPropagation();
878
+ if (this.checkpointService?.busy())
879
+ return;
880
+ this.deletingCheckpointId.set(checkpoint.id);
881
+ this.editingCheckpointId.set(null);
882
+ }
883
+ cancelDelete(event) {
884
+ event?.stopPropagation();
885
+ this.deletingCheckpointId.set(null);
886
+ this.checkpointSearch()?.nativeElement.focus();
887
+ }
888
+ async confirmDelete(checkpoint, event) {
889
+ event?.stopPropagation();
890
+ if (!await this.checkpointService?.delete(checkpoint.id))
891
+ return;
892
+ this.deletingCheckpointId.set(null);
893
+ this.clampActiveCheckpoint();
894
+ this.checkpointSearch()?.nativeElement.focus();
895
+ }
896
+ handleCheckpointDialogClick(event) {
897
+ if (event.target === this.checkpointDialog()?.nativeElement &&
898
+ !this.checkpointService?.busy() &&
899
+ !this.deletingCheckpointId()) {
900
+ this.closeCheckpointCommandBar();
901
+ }
405
902
  }
406
903
  clearGuides() {
407
904
  this.guides.set([]);
@@ -422,18 +919,27 @@ class inspectorComponent {
422
919
  }
423
920
  handleKeydown(event) {
424
921
  const key = event.key.toLowerCase();
425
- if (key === 'm') {
922
+ if (this.checkpointService &&
923
+ !event.repeat &&
924
+ (event.metaKey || event.ctrlKey) &&
925
+ event.shiftKey &&
926
+ key === 'p') {
426
927
  event.preventDefault();
427
- this.enabled.update((value) => !value);
428
- if (!this.enabled()) {
429
- this.toolMode.set('none');
430
- this.hoverRect.set(null);
431
- this.selectedGuideId.set(null);
432
- this.distanceOverlay.set(null);
433
- this.textBlocks.set([]);
434
- this.guidePreview.set(null);
928
+ this.commandBarOpen() ? this.closeCheckpointCommandBar() : void this.openCheckpointCommandBar();
929
+ return;
930
+ }
931
+ if (this.commandBarOpen()) {
932
+ this.handleCommandBarKeydown(event, key);
933
+ return;
934
+ }
935
+ if (key === 'm') {
936
+ const target = event.target;
937
+ if (target?.matches('input, textarea, select') ||
938
+ target?.isContentEditable) {
939
+ return;
435
940
  }
436
- this.refreshTypographyBlocks();
941
+ event.preventDefault();
942
+ this.setInspectorEnabled(!this.enabled());
437
943
  return;
438
944
  }
439
945
  if (!this.enabled()) {
@@ -588,6 +1094,94 @@ class inspectorComponent {
588
1094
  const overlay = this.overlayRoot()?.nativeElement ?? null;
589
1095
  return !(overlay && overlay.contains(event.target));
590
1096
  }
1097
+ setInspectorEnabled(enabled) {
1098
+ this.enabled.set(enabled);
1099
+ if (!enabled) {
1100
+ this.toolMode.set('none');
1101
+ this.hoverRect.set(null);
1102
+ this.selectedGuideId.set(null);
1103
+ this.selectedMeasurement.set(null);
1104
+ this.distanceOverlay.set(null);
1105
+ this.textBlocks.set([]);
1106
+ this.guidePreview.set(null);
1107
+ this.altPressed.set(false);
1108
+ this.selectedElement = null;
1109
+ this.hoverElement = null;
1110
+ this.draggingGuideId = null;
1111
+ }
1112
+ this.refreshTypographyBlocks();
1113
+ }
1114
+ handleCommandBarKeydown(event, key) {
1115
+ const checkpoints = this.filteredCheckpoints();
1116
+ const active = checkpoints[this.activeCheckpointIndex()];
1117
+ const target = event.target;
1118
+ const isRenameInput = target?.classList?.contains('inspector-command-row__rename') ?? false;
1119
+ if (key === 'escape') {
1120
+ event.preventDefault();
1121
+ if (this.editingCheckpointId())
1122
+ this.cancelRename();
1123
+ else if (this.deletingCheckpointId())
1124
+ this.cancelDelete();
1125
+ else
1126
+ this.closeCheckpointCommandBar();
1127
+ return;
1128
+ }
1129
+ if (this.checkpointService?.busy())
1130
+ return;
1131
+ const editingCheckpoint = checkpoints.find(({ id }) => id === this.editingCheckpointId());
1132
+ if (isRenameInput && key === 'enter' && editingCheckpoint) {
1133
+ event.preventDefault();
1134
+ void this.finishRename(editingCheckpoint);
1135
+ return;
1136
+ }
1137
+ if (isRenameInput)
1138
+ return;
1139
+ if (key === 'arrowdown') {
1140
+ event.preventDefault();
1141
+ this.activeCheckpointIndex.set(Math.min(this.activeCheckpointIndex() + 1, Math.max(0, checkpoints.length - 1)));
1142
+ this.scrollActiveCheckpointIntoView();
1143
+ return;
1144
+ }
1145
+ if (key === 'arrowup') {
1146
+ event.preventDefault();
1147
+ this.activeCheckpointIndex.set(Math.max(0, this.activeCheckpointIndex() - 1));
1148
+ this.scrollActiveCheckpointIntoView();
1149
+ return;
1150
+ }
1151
+ if (key === 'enter' && active && !this.deletingCheckpointId()) {
1152
+ event.preventDefault();
1153
+ void this.restoreCheckpoint(active);
1154
+ return;
1155
+ }
1156
+ if (key === 'f2' && active) {
1157
+ event.preventDefault();
1158
+ this.beginRename(active);
1159
+ return;
1160
+ }
1161
+ if (key === 'delete' && active) {
1162
+ event.preventDefault();
1163
+ this.beginDelete(active);
1164
+ }
1165
+ }
1166
+ clampActiveCheckpoint() {
1167
+ this.activeCheckpointIndex.set(Math.min(this.activeCheckpointIndex(), Math.max(0, this.filteredCheckpoints().length - 1)));
1168
+ }
1169
+ scrollActiveCheckpointIntoView() {
1170
+ queueMicrotask(() => {
1171
+ const row = this.checkpointDialog()?.nativeElement.querySelector(`[data-checkpoint-index="${this.activeCheckpointIndex()}"]`);
1172
+ row?.scrollIntoView({ block: 'nearest' });
1173
+ });
1174
+ }
1175
+ showCheckpointToast(message, isError = false) {
1176
+ if (this.toastTimer)
1177
+ clearTimeout(this.toastTimer);
1178
+ this.checkpointToastIsError.set(isError);
1179
+ this.checkpointToast.set(message);
1180
+ this.toastTimer = setTimeout(() => {
1181
+ this.checkpointToast.set(null);
1182
+ this.checkpointToastIsError.set(false);
1183
+ }, 2500);
1184
+ }
591
1185
  selectElementAtPoint(event) {
592
1186
  const overlay = this.overlayRoot()?.nativeElement ?? null;
593
1187
  const target = getTargetElement({ x: event.clientX, y: event.clientY }, overlay);
@@ -732,11 +1326,11 @@ class inspectorComponent {
732
1326
  return null;
733
1327
  }
734
1328
  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 }); }
735
- 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 }); }
1329
+ 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 }, { propertyName: "checkpointDialog", first: true, predicate: ["checkpointDialog"], descendants: true, isSignal: true }, { propertyName: "checkpointSearch", first: true, predicate: ["checkpointSearch"], 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 (checkpointService) {\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n (click)=\"saveCheckpoint()\"\n [disabled]=\"checkpointService.busy()\"\n aria-label=\"Save checkpoint\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M2.5 2h9.6l1.4 1.4V14h-11zm2 1.5v3h6v-3zm.5 6V12h6V9.5z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Save checkpoint</span>\n </button>\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 (checkpointService) {\n <dialog\n #checkpointDialog\n class=\"inspector-command-dialog\"\n aria-labelledby=\"inspector-command-title\"\n (cancel)=\"$event.preventDefault(); closeCheckpointCommandBar()\"\n (click)=\"handleCheckpointDialogClick($event)\"\n >\n <section class=\"inspector-command\" (click)=\"$event.stopPropagation()\">\n <header class=\"inspector-command__search\">\n <label id=\"inspector-command-title\" class=\"inspector-visually-hidden\" for=\"inspector-checkpoint-search\">\n Search checkpoints\n </label>\n <input\n #checkpointSearch\n id=\"inspector-checkpoint-search\"\n type=\"search\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n placeholder=\"Find a checkpoint\u2026\"\n [value]=\"checkpointQuery()\"\n [attr.aria-controls]=\"'inspector-checkpoint-results'\"\n [attr.aria-activedescendant]=\"filteredCheckpoints().length ? 'inspector-checkpoint-' + filteredCheckpoints()[activeCheckpointIndex()].id : null\"\n (input)=\"updateCheckpointQuery($event)\"\n />\n </header>\n\n @if (checkpointService.error(); as error) {\n <p class=\"inspector-command__message inspector-command__message--error\" role=\"alert\">{{ error }}</p>\n }\n\n <div id=\"inspector-checkpoint-results\" class=\"inspector-command__results\" role=\"listbox\" aria-label=\"Saved checkpoints\">\n @if (checkpointService.loading()) {\n <p class=\"inspector-command__message\" role=\"status\">Loading checkpoints\u2026</p>\n } @else if (!checkpointService.checkpoints().length) {\n <p class=\"inspector-command__message\" role=\"status\">No checkpoints yet. Save one from the Inspector toolbar.</p>\n } @else if (!filteredCheckpoints().length) {\n <p class=\"inspector-command__message\" role=\"status\">No checkpoint matches \u201C{{ checkpointQuery() }}\u201D.</p>\n } @else {\n @for (checkpoint of filteredCheckpoints(); track checkpoint.id; let index = $index) {\n <div\n class=\"inspector-command-row\"\n role=\"option\"\n [id]=\"'inspector-checkpoint-' + checkpoint.id\"\n [attr.data-checkpoint-index]=\"index\"\n [attr.aria-selected]=\"activeCheckpointIndex() === index\"\n [class.is-active]=\"activeCheckpointIndex() === index\"\n [class.is-busy]=\"checkpointService.busy() && activeCheckpointIndex() === index\"\n (mouseenter)=\"setActiveCheckpoint(index)\"\n (click)=\"deletingCheckpointId() === checkpoint.id || editingCheckpointId() === checkpoint.id ? null : restoreCheckpoint(checkpoint)\"\n >\n @if (deletingCheckpointId() === checkpoint.id) {\n <span class=\"inspector-command-row__confirm\">Delete \u201C{{ checkpoint.name }}\u201D?</span>\n <span class=\"inspector-command-row__confirm-actions\">\n <button type=\"button\" class=\"is-danger\" (click)=\"confirmDelete(checkpoint, $event)\">Delete checkpoint</button>\n <button type=\"button\" (click)=\"cancelDelete($event)\">Keep checkpoint</button>\n </span>\n } @else if (editingCheckpointId() === checkpoint.id) {\n <input\n class=\"inspector-command-row__rename\"\n aria-label=\"Checkpoint name\"\n [value]=\"renameDraft()\"\n (input)=\"updateRenameDraft($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n } @else {\n <span class=\"inspector-command-row__name\">\n @if (checkpointService.busy() && activeCheckpointIndex() === index) {\n Restoring \u201C{{ checkpoint.name }}\u201D\u2026\n } @else {\n {{ checkpoint.name }}\n }\n </span>\n <span class=\"inspector-command-row__actions\">\n <button type=\"button\" (click)=\"beginRename(checkpoint, $event)\" [attr.aria-label]=\"'Rename ' + checkpoint.name\">\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"m11.8 1.8 2.4 2.4-8.3 8.3-3.1.7.7-3.1zm-8 10.4 1.5-.3-1.2-1.2z\" /></svg>\n </button>\n <button type=\"button\" (click)=\"beginDelete(checkpoint, $event)\" [attr.aria-label]=\"'Delete ' + checkpoint.name\">\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"M5.5 2h5l.5 1H14v1.25H2V3h3zm-2 3.25h9L12 14H4z\" /></svg>\n </button>\n </span>\n }\n </div>\n }\n }\n </div>\n </section>\n </dialog>\n }\n\n @if (checkpointToast(); as toast) {\n <p\n class=\"inspector-checkpoint-toast\"\n [class.inspector-checkpoint-toast--error]=\"checkpointToastIsError()\"\n [attr.role]=\"checkpointToastIsError() ? 'alert' : 'status'\"\n >{{ toast }}</p>\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-command-dialog{--command-surface: oklch(20% .012 270);--command-surface-raised: oklch(25% .014 270);--command-surface-active: oklch(27% .018 270);--command-edge: oklch(39% .014 270);--command-text: oklch(95% .008 270);--command-muted: oklch(73% .018 270);--command-accent: oklch(76% .08 260);--command-danger: oklch(71% .17 25);box-sizing:border-box;inset-block-start:max(40px,10vh);width:min(480px,calc(100vw - 24px));max-width:none;max-height:min(400px,calc(100vh - 64px));margin:0 auto;padding:0;overflow:hidden;border:1px solid var(--command-edge);border-radius:4px;background:var(--command-surface);color:var(--command-text);box-shadow:0 20px 56px #0102038f;opacity:0;pointer-events:auto;transform:translateY(-6px);transition:opacity .12s cubic-bezier(.7,0,.84,0),transform .12s cubic-bezier(.7,0,.84,0),overlay .12s allow-discrete,display .12s allow-discrete}.inspector-command-dialog[open]{opacity:1;transform:translateY(0);transition-duration:.16s;transition-timing-function:cubic-bezier(.16,1,.3,1)}@starting-style{.inspector-command-dialog[open]{opacity:0;transform:translateY(-6px)}}.inspector-command-dialog::backdrop{background:#0102037a}.inspector-command{display:grid;min-width:0}.inspector-command__search{min-height:44px;padding-inline:12px;border-bottom:1px solid var(--command-edge)}.inspector-command__search input,.inspector-command-row__rename{min-width:0;border:0;outline:0;background:transparent;color:var(--command-text);font:inherit}.inspector-command__search input{width:100%;height:43px;padding:0;font-size:14px;line-height:1.45}.inspector-command__search:has(input:focus-visible){box-shadow:inset 0 -2px var(--command-accent)}.inspector-command__search input::placeholder{color:var(--command-muted);opacity:1}.inspector-command__search input::-webkit-search-cancel-button{display:none}.inspector-command__results{max-height:304px;overflow:auto;padding-block:3px;scrollbar-color:var(--command-edge) transparent}.inspector-command-row{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px;min-height:38px;padding-inline:12px 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.inspector-command-row.is-active{background:var(--command-surface-active)}.inspector-command-row.is-busy{cursor:wait;pointer-events:none}.inspector-command-row__name{overflow:hidden;color:var(--command-text);font-size:12px;font-weight:540;line-height:1.45;text-overflow:ellipsis;white-space:nowrap}.inspector-command-row.is-active .inspector-command-row__name{font-weight:700}.inspector-command-row__actions,.inspector-command-row__confirm-actions{display:flex;align-items:center;gap:4px}.inspector-command-row__actions{opacity:0;pointer-events:none}.inspector-command-row:hover .inspector-command-row__actions,.inspector-command-row:focus-within .inspector-command-row__actions{opacity:1;pointer-events:auto}.inspector-command-row button{display:inline-grid;width:26px;min-height:26px;padding:5px;place-items:center;border:1px solid transparent;border-radius:3px;background:transparent;color:var(--command-muted);cursor:pointer;font-family:inherit;font-size:10px;font-weight:600;line-height:1.3}.inspector-command-row button svg{width:12px;height:12px;fill:currentColor}.inspector-command-row button:hover{border-color:var(--command-edge);background:var(--command-surface-raised);color:var(--command-text)}.inspector-command-row button.is-danger{color:var(--command-danger)}.inspector-command-row button:focus-visible,.inspector-command-row__rename:focus-visible{outline:2px solid var(--command-accent);outline-offset:2px}.inspector-command-row__confirm{grid-column:1;color:var(--command-text);font-size:12px;font-weight:620}.inspector-command-row__rename{grid-column:1 / -1;min-height:30px;padding-inline:8px;border:1px solid var(--command-accent);border-radius:3px;background:var(--command-surface-raised);font-size:12px}.inspector-command__message{margin:0;padding:20px 12px;color:var(--command-muted);font-size:12px;line-height:1.55;text-align:center}.inspector-command__message--error{padding-block:9px;border-bottom:1px solid oklch(45% .1 25);background:#3f1917;color:oklch(89% .06 25);text-align:left}.inspector-visually-hidden{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.inspector-checkpoint-toast{position:fixed;top:12px;left:50%;z-index:500;margin:0;padding:8px 12px;border:1px solid oklch(48% .04 150);border-radius:4px;background:#152619;color:#cfe9d4;box-shadow:0 16px 40px #01020375;font-size:11px;font-weight:620;line-height:1.35;pointer-events:none;transform:translate(-50%)}.inspector-checkpoint-toast--error{border-color:#9c433f;background:#431614;color:oklch(92% .05 25)}.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,.inspector-command-dialog,.inspector-command-row__marker{transition-duration:.01ms}.inspector-command-dialog,.inspector-command-dialog[open]{transform:none}}.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 }); }
736
1330
  }
737
1331
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: inspectorComponent, decorators: [{
738
1332
  type: Component,
739
- 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"] }]
1333
+ 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 (checkpointService) {\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n (click)=\"saveCheckpoint()\"\n [disabled]=\"checkpointService.busy()\"\n aria-label=\"Save checkpoint\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M2.5 2h9.6l1.4 1.4V14h-11zm2 1.5v3h6v-3zm.5 6V12h6V9.5z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Save checkpoint</span>\n </button>\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 (checkpointService) {\n <dialog\n #checkpointDialog\n class=\"inspector-command-dialog\"\n aria-labelledby=\"inspector-command-title\"\n (cancel)=\"$event.preventDefault(); closeCheckpointCommandBar()\"\n (click)=\"handleCheckpointDialogClick($event)\"\n >\n <section class=\"inspector-command\" (click)=\"$event.stopPropagation()\">\n <header class=\"inspector-command__search\">\n <label id=\"inspector-command-title\" class=\"inspector-visually-hidden\" for=\"inspector-checkpoint-search\">\n Search checkpoints\n </label>\n <input\n #checkpointSearch\n id=\"inspector-checkpoint-search\"\n type=\"search\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n placeholder=\"Find a checkpoint\u2026\"\n [value]=\"checkpointQuery()\"\n [attr.aria-controls]=\"'inspector-checkpoint-results'\"\n [attr.aria-activedescendant]=\"filteredCheckpoints().length ? 'inspector-checkpoint-' + filteredCheckpoints()[activeCheckpointIndex()].id : null\"\n (input)=\"updateCheckpointQuery($event)\"\n />\n </header>\n\n @if (checkpointService.error(); as error) {\n <p class=\"inspector-command__message inspector-command__message--error\" role=\"alert\">{{ error }}</p>\n }\n\n <div id=\"inspector-checkpoint-results\" class=\"inspector-command__results\" role=\"listbox\" aria-label=\"Saved checkpoints\">\n @if (checkpointService.loading()) {\n <p class=\"inspector-command__message\" role=\"status\">Loading checkpoints\u2026</p>\n } @else if (!checkpointService.checkpoints().length) {\n <p class=\"inspector-command__message\" role=\"status\">No checkpoints yet. Save one from the Inspector toolbar.</p>\n } @else if (!filteredCheckpoints().length) {\n <p class=\"inspector-command__message\" role=\"status\">No checkpoint matches \u201C{{ checkpointQuery() }}\u201D.</p>\n } @else {\n @for (checkpoint of filteredCheckpoints(); track checkpoint.id; let index = $index) {\n <div\n class=\"inspector-command-row\"\n role=\"option\"\n [id]=\"'inspector-checkpoint-' + checkpoint.id\"\n [attr.data-checkpoint-index]=\"index\"\n [attr.aria-selected]=\"activeCheckpointIndex() === index\"\n [class.is-active]=\"activeCheckpointIndex() === index\"\n [class.is-busy]=\"checkpointService.busy() && activeCheckpointIndex() === index\"\n (mouseenter)=\"setActiveCheckpoint(index)\"\n (click)=\"deletingCheckpointId() === checkpoint.id || editingCheckpointId() === checkpoint.id ? null : restoreCheckpoint(checkpoint)\"\n >\n @if (deletingCheckpointId() === checkpoint.id) {\n <span class=\"inspector-command-row__confirm\">Delete \u201C{{ checkpoint.name }}\u201D?</span>\n <span class=\"inspector-command-row__confirm-actions\">\n <button type=\"button\" class=\"is-danger\" (click)=\"confirmDelete(checkpoint, $event)\">Delete checkpoint</button>\n <button type=\"button\" (click)=\"cancelDelete($event)\">Keep checkpoint</button>\n </span>\n } @else if (editingCheckpointId() === checkpoint.id) {\n <input\n class=\"inspector-command-row__rename\"\n aria-label=\"Checkpoint name\"\n [value]=\"renameDraft()\"\n (input)=\"updateRenameDraft($event)\"\n (click)=\"$event.stopPropagation()\"\n />\n } @else {\n <span class=\"inspector-command-row__name\">\n @if (checkpointService.busy() && activeCheckpointIndex() === index) {\n Restoring \u201C{{ checkpoint.name }}\u201D\u2026\n } @else {\n {{ checkpoint.name }}\n }\n </span>\n <span class=\"inspector-command-row__actions\">\n <button type=\"button\" (click)=\"beginRename(checkpoint, $event)\" [attr.aria-label]=\"'Rename ' + checkpoint.name\">\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"m11.8 1.8 2.4 2.4-8.3 8.3-3.1.7.7-3.1zm-8 10.4 1.5-.3-1.2-1.2z\" /></svg>\n </button>\n <button type=\"button\" (click)=\"beginDelete(checkpoint, $event)\" [attr.aria-label]=\"'Delete ' + checkpoint.name\">\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"M5.5 2h5l.5 1H14v1.25H2V3h3zm-2 3.25h9L12 14H4z\" /></svg>\n </button>\n </span>\n }\n </div>\n }\n }\n </div>\n </section>\n </dialog>\n }\n\n @if (checkpointToast(); as toast) {\n <p\n class=\"inspector-checkpoint-toast\"\n [class.inspector-checkpoint-toast--error]=\"checkpointToastIsError()\"\n [attr.role]=\"checkpointToastIsError() ? 'alert' : 'status'\"\n >{{ toast }}</p>\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-command-dialog{--command-surface: oklch(20% .012 270);--command-surface-raised: oklch(25% .014 270);--command-surface-active: oklch(27% .018 270);--command-edge: oklch(39% .014 270);--command-text: oklch(95% .008 270);--command-muted: oklch(73% .018 270);--command-accent: oklch(76% .08 260);--command-danger: oklch(71% .17 25);box-sizing:border-box;inset-block-start:max(40px,10vh);width:min(480px,calc(100vw - 24px));max-width:none;max-height:min(400px,calc(100vh - 64px));margin:0 auto;padding:0;overflow:hidden;border:1px solid var(--command-edge);border-radius:4px;background:var(--command-surface);color:var(--command-text);box-shadow:0 20px 56px #0102038f;opacity:0;pointer-events:auto;transform:translateY(-6px);transition:opacity .12s cubic-bezier(.7,0,.84,0),transform .12s cubic-bezier(.7,0,.84,0),overlay .12s allow-discrete,display .12s allow-discrete}.inspector-command-dialog[open]{opacity:1;transform:translateY(0);transition-duration:.16s;transition-timing-function:cubic-bezier(.16,1,.3,1)}@starting-style{.inspector-command-dialog[open]{opacity:0;transform:translateY(-6px)}}.inspector-command-dialog::backdrop{background:#0102037a}.inspector-command{display:grid;min-width:0}.inspector-command__search{min-height:44px;padding-inline:12px;border-bottom:1px solid var(--command-edge)}.inspector-command__search input,.inspector-command-row__rename{min-width:0;border:0;outline:0;background:transparent;color:var(--command-text);font:inherit}.inspector-command__search input{width:100%;height:43px;padding:0;font-size:14px;line-height:1.45}.inspector-command__search:has(input:focus-visible){box-shadow:inset 0 -2px var(--command-accent)}.inspector-command__search input::placeholder{color:var(--command-muted);opacity:1}.inspector-command__search input::-webkit-search-cancel-button{display:none}.inspector-command__results{max-height:304px;overflow:auto;padding-block:3px;scrollbar-color:var(--command-edge) transparent}.inspector-command-row{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:6px;min-height:38px;padding-inline:12px 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.inspector-command-row.is-active{background:var(--command-surface-active)}.inspector-command-row.is-busy{cursor:wait;pointer-events:none}.inspector-command-row__name{overflow:hidden;color:var(--command-text);font-size:12px;font-weight:540;line-height:1.45;text-overflow:ellipsis;white-space:nowrap}.inspector-command-row.is-active .inspector-command-row__name{font-weight:700}.inspector-command-row__actions,.inspector-command-row__confirm-actions{display:flex;align-items:center;gap:4px}.inspector-command-row__actions{opacity:0;pointer-events:none}.inspector-command-row:hover .inspector-command-row__actions,.inspector-command-row:focus-within .inspector-command-row__actions{opacity:1;pointer-events:auto}.inspector-command-row button{display:inline-grid;width:26px;min-height:26px;padding:5px;place-items:center;border:1px solid transparent;border-radius:3px;background:transparent;color:var(--command-muted);cursor:pointer;font-family:inherit;font-size:10px;font-weight:600;line-height:1.3}.inspector-command-row button svg{width:12px;height:12px;fill:currentColor}.inspector-command-row button:hover{border-color:var(--command-edge);background:var(--command-surface-raised);color:var(--command-text)}.inspector-command-row button.is-danger{color:var(--command-danger)}.inspector-command-row button:focus-visible,.inspector-command-row__rename:focus-visible{outline:2px solid var(--command-accent);outline-offset:2px}.inspector-command-row__confirm{grid-column:1;color:var(--command-text);font-size:12px;font-weight:620}.inspector-command-row__rename{grid-column:1 / -1;min-height:30px;padding-inline:8px;border:1px solid var(--command-accent);border-radius:3px;background:var(--command-surface-raised);font-size:12px}.inspector-command__message{margin:0;padding:20px 12px;color:var(--command-muted);font-size:12px;line-height:1.55;text-align:center}.inspector-command__message--error{padding-block:9px;border-bottom:1px solid oklch(45% .1 25);background:#3f1917;color:oklch(89% .06 25);text-align:left}.inspector-visually-hidden{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.inspector-checkpoint-toast{position:fixed;top:12px;left:50%;z-index:500;margin:0;padding:8px 12px;border:1px solid oklch(48% .04 150);border-radius:4px;background:#152619;color:#cfe9d4;box-shadow:0 16px 40px #01020375;font-size:11px;font-weight:620;line-height:1.35;pointer-events:none;transform:translate(-50%)}.inspector-checkpoint-toast--error{border-color:#9c433f;background:#431614;color:oklch(92% .05 25)}.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,.inspector-command-dialog,.inspector-command-row__marker{transition-duration:.01ms}.inspector-command-dialog,.inspector-command-dialog[open]{transform:none}}.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"] }]
740
1334
  }], ctorParameters: () => [{ type: undefined, decorators: [{
741
1335
  type: Inject,
742
1336
  args: [PLATFORM_ID]
@@ -775,5 +1369,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
775
1369
  * Generated bundle index. Do not edit.
776
1370
  */
777
1371
 
778
- 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 };
1372
+ 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, provideInspectorCheckpoints, rectContainsPoint, rectsEqual };
779
1373
  //# sourceMappingURL=inspector-ng.mjs.map