inspector-ng 0.0.7 → 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,274 +1,330 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, PLATFORM_ID, Inject, Injectable, viewChild, inject, effect, HostListener, Input, 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 i1 from '@angular/router';
4
+ import * as i2 from '@angular/router';
5
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';
6
9
 
7
10
  const inspector_STORAGE_KEY = "inspector-state";
8
11
  const inspector_STATE_VERSION = 2;
9
12
  const GUIDE_SNAP_DISTANCE = 10;
10
13
  const GUIDE_HITBOX_SIZE = 14;
11
14
 
12
- const STORAGE_KEY = 'inspector-checkpoints-v1';
13
- class InspectorCheckpointRegistry {
14
- constructor(platformId, router) {
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;
15
100
  this.router = router;
101
+ this.platformId = platformId;
16
102
  this.checkpoints = signal([]);
17
- this.isBusy = signal(false);
103
+ this.loading = signal(false);
104
+ this.busy = signal(false);
18
105
  this.error = signal(null);
19
- this.warning = signal(null);
20
- this.totalBytes = computed(() => this.checkpoints().reduce((total, checkpoint) => total + checkpoint.sizeBytes, 0));
21
- this.adapters = new Map();
22
- this.remoteLoaders = new Map();
23
- this.isCheckpoint = (value) => {
24
- if (!value || typeof value !== 'object')
25
- return false;
26
- const checkpoint = value;
27
- return checkpoint.version === 1 && typeof checkpoint.id === 'string' &&
28
- typeof checkpoint.name === 'string' && typeof checkpoint.url === 'string' &&
29
- typeof checkpoint.createdAt === 'string' && Array.isArray(checkpoint.scopes) &&
30
- !!checkpoint.state && typeof checkpoint.state === 'object' && typeof checkpoint.sizeBytes === 'number';
31
- };
32
- this.isBrowser = isPlatformBrowser(platformId);
33
- this.hydrate();
34
106
  }
35
- register(adapter) {
36
- if (!adapter.id.includes(':')) {
37
- throw new Error('Checkpoint adapter IDs must be namespaced (for example, workflow:application).');
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));
38
112
  }
39
- if (this.adapters.has(adapter.id)) {
40
- // Remote bridge loaders can run more than once across checkpoint restores.
41
- // Treat repeated registration as idempotent so a loaded remote stays usable.
42
- return () => undefined;
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);
43
118
  }
44
- this.adapters.set(adapter.id, adapter);
45
- return () => this.adapters.delete(adapter.id);
46
- }
47
- registerRemoteScope(scope, loader) {
48
- this.remoteLoaders.set(scope, loader);
49
- return () => this.remoteLoaders.delete(scope);
50
119
  }
51
- async save(name) {
120
+ async save() {
52
121
  this.error.set(null);
53
- if (!this.isBrowser) {
54
- this.error.set('Checkpoints are only available in a browser.');
122
+ if (!this.store) {
123
+ this.error.set(this.missingStoreMessage());
55
124
  return null;
56
125
  }
57
- this.isBusy.set(true);
126
+ this.busy.set(true);
58
127
  try {
59
- const state = {};
60
- for (const adapter of this.adapters.values()) {
61
- state[adapter.id] = this.cloneValue(await adapter.capture(), adapter.id);
62
- }
63
- const createdAt = new Date().toISOString();
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();
64
133
  const checkpoint = {
65
134
  version: 1,
66
- id: this.createId(),
67
- name: name?.trim() || this.nextAutomaticName(this.routePath(this.router.url)),
68
- url: this.router.url,
69
- createdAt,
70
- scopes: this.getScopes(Object.keys(state)),
135
+ id: createCheckpointId(),
136
+ name: nextAutomaticName(pathname, persisted.map(({ name }) => name)),
137
+ route,
138
+ createdAt: new Date().toISOString(),
71
139
  state,
72
- sizeBytes: 0,
73
140
  };
74
- checkpoint.sizeBytes = this.sizeOf(checkpoint);
75
- this.persistWithEviction([checkpoint, ...this.checkpoints()]);
76
- return this.checkpoints().find((item) => item.id === checkpoint.id) ?? null;
141
+ await this.repository.put(checkpoint);
142
+ this.checkpoints.set([checkpoint, ...persisted].sort(newestFirst$1));
143
+ return checkpoint;
77
144
  }
78
145
  catch (error) {
79
- this.error.set(this.messageFor(error, 'Unable to save checkpoint.'));
146
+ this.error.set(this.storageMessage(error, 'The checkpoint could not be saved.'));
80
147
  return null;
81
148
  }
82
149
  finally {
83
- this.isBusy.set(false);
150
+ this.busy.set(false);
84
151
  }
85
152
  }
86
153
  async restore(id) {
87
- const checkpoint = this.checkpoints().find((item) => item.id === id);
88
154
  this.error.set(null);
89
- this.warning.set(null);
90
- if (!checkpoint) {
91
- this.error.set('Checkpoint not found.');
155
+ if (!this.store) {
156
+ this.error.set(this.missingStoreMessage());
92
157
  return false;
93
158
  }
94
- this.isBusy.set(true);
159
+ this.busy.set(true);
95
160
  try {
96
- for (const scope of checkpoint.scopes) {
97
- const loader = this.remoteLoaders.get(scope);
98
- if (loader) {
99
- await loader();
100
- }
101
- }
102
- const missing = [];
103
- for (const [adapterId, value] of Object.entries(checkpoint.state)) {
104
- const adapter = this.adapters.get(adapterId);
105
- if (!adapter) {
106
- missing.push(adapterId);
107
- continue;
108
- }
109
- await adapter.restore(this.cloneValue(value, adapterId));
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.`);
110
168
  }
111
- if (missing.length) {
112
- this.warning.set(`Skipped ${missing.length} unavailable adapter${missing.length === 1 ? '' : 's'}: ${missing.join(', ')}.`);
113
- }
114
- await this.router.navigateByUrl(checkpoint.url);
115
169
  return true;
116
170
  }
117
171
  catch (error) {
118
- this.error.set(this.messageFor(error, 'Unable to restore checkpoint.'));
172
+ this.error.set(this.messageFor(error, 'The checkpoint could not be restored. Delete it and save a new one.'));
119
173
  return false;
120
174
  }
121
175
  finally {
122
- this.isBusy.set(false);
176
+ this.busy.set(false);
123
177
  }
124
178
  }
125
- rename(id, name) {
179
+ async rename(id, name) {
180
+ this.error.set(null);
126
181
  const nextName = name.trim();
127
182
  if (!nextName) {
128
- this.error.set('A checkpoint name is required.');
129
- return;
130
- }
131
- this.error.set(null);
132
- const next = this.checkpoints().map((checkpoint) => checkpoint.id === id ? { ...checkpoint, name: nextName } : checkpoint);
133
- this.persistWithEviction(next);
134
- }
135
- delete(id) {
136
- this.error.set(null);
137
- this.persistWithEviction(this.checkpoints().filter((checkpoint) => checkpoint.id !== id));
138
- }
139
- hydrate() {
140
- if (!this.isBrowser) {
141
- return;
183
+ this.error.set('Please enter a checkpoint name.');
184
+ return false;
142
185
  }
186
+ this.busy.set(true);
143
187
  try {
144
- const raw = window.localStorage.getItem(STORAGE_KEY);
145
- if (!raw)
146
- return;
147
- const parsed = JSON.parse(raw);
148
- if (!Array.isArray(parsed))
149
- return;
150
- const checkpoints = parsed.filter(this.isCheckpoint);
151
- const normalized = this.normalizeLegacyNames(checkpoints);
152
- const sorted = normalized.checkpoints.sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id));
153
- this.checkpoints.set(sorted);
154
- if (normalized.changed) {
155
- window.localStorage.setItem(STORAGE_KEY, JSON.stringify(sorted));
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.`);
156
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;
157
198
  }
158
- catch {
159
- this.warning.set('Saved checkpoints could not be read and were ignored.');
199
+ catch (error) {
200
+ this.error.set(this.storageMessage(error, 'The checkpoint could not be renamed.'));
201
+ return false;
160
202
  }
161
- }
162
- persistWithEviction(checkpoints) {
163
- if (!this.isBrowser)
164
- return;
165
- let candidate = checkpoints;
166
- while (true) {
167
- try {
168
- window.localStorage.setItem(STORAGE_KEY, JSON.stringify(candidate));
169
- this.checkpoints.set(candidate);
170
- return;
171
- }
172
- catch (error) {
173
- if (!this.isQuotaError(error) || candidate.length === 0) {
174
- throw error;
175
- }
176
- candidate = candidate.slice(0, -1);
177
- this.warning.set('Storage was full; the oldest checkpoint was removed.');
178
- }
203
+ finally {
204
+ this.busy.set(false);
179
205
  }
180
206
  }
181
- cloneValue(value, adapterId) {
207
+ async delete(id) {
208
+ this.error.set(null);
209
+ this.busy.set(true);
182
210
  try {
183
- const serialized = JSON.stringify(value);
184
- if (serialized === undefined)
185
- throw new Error('undefined values are not supported');
186
- return JSON.parse(serialized);
211
+ await this.repository.delete(id);
212
+ this.checkpoints.set(this.checkpoints().filter((item) => item.id !== id));
213
+ return true;
187
214
  }
188
- catch {
189
- throw new Error(`Adapter "${adapterId}" returned a value that cannot be JSON serialized.`);
190
- }
191
- }
192
- getScopes(adapterIds) {
193
- return [...new Set(adapterIds.map((id) => id.split(':', 1)[0]))];
194
- }
195
- routePath(url) {
196
- return url.split(/[?#]/, 1)[0] || '/';
197
- }
198
- nextAutomaticName(route, usedNames = new Set(this.checkpoints().map(({ name }) => name))) {
199
- if (!usedNames.has(route))
200
- return route;
201
- for (let suffix = 2;; suffix += 1) {
202
- const candidate = `${route} ${suffix}`;
203
- if (!usedNames.has(candidate))
204
- return candidate;
205
- }
206
- }
207
- normalizeLegacyNames(checkpoints) {
208
- const legacyIds = new Set(checkpoints.filter((checkpoint) => this.isLegacyAutomaticName(checkpoint)).map(({ id }) => id));
209
- if (!legacyIds.size)
210
- return { checkpoints, changed: false };
211
- const usedNames = new Set(checkpoints.filter(({ id }) => !legacyIds.has(id)).map(({ name }) => name));
212
- const names = new Map();
213
- const oldestFirst = checkpoints
214
- .filter(({ id }) => legacyIds.has(id))
215
- .sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
216
- for (const checkpoint of oldestFirst) {
217
- const name = this.nextAutomaticName(this.routePath(checkpoint.url), usedNames);
218
- names.set(checkpoint.id, name);
219
- usedNames.add(name);
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);
220
221
  }
221
- return {
222
- checkpoints: checkpoints.map((checkpoint) => names.has(checkpoint.id) ? { ...checkpoint, name: names.get(checkpoint.id) } : checkpoint),
223
- changed: true,
224
- };
225
222
  }
226
- isLegacyAutomaticName(checkpoint) {
227
- const route = this.routePath(checkpoint.url);
228
- const prefix = `${route} · `;
229
- if (!checkpoint.name.startsWith(prefix))
230
- return false;
231
- const timestamp = checkpoint.name.slice(prefix.length);
232
- if (timestamp === new Date(checkpoint.createdAt).toLocaleString())
233
- return true;
234
- const parsedTimestamp = Date.parse(timestamp);
235
- const createdTimestamp = Date.parse(checkpoint.createdAt);
236
- return Number.isFinite(parsedTimestamp) && Number.isFinite(createdTimestamp) &&
237
- Math.abs(parsedTimestamp - createdTimestamp) < 1000;
223
+ clearError() {
224
+ this.error.set(null);
238
225
  }
239
- createId() {
240
- return `checkpoint-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
226
+ currentRoute() {
227
+ if (!isPlatformBrowser(this.platformId))
228
+ return '/';
229
+ return `${this.currentPathname()}${window.location.search}${window.location.hash}`;
241
230
  }
242
- sizeOf(checkpoint) {
243
- return new Blob([JSON.stringify(checkpoint)]).size;
231
+ currentPathname() {
232
+ if (!isPlatformBrowser(this.platformId))
233
+ return '/';
234
+ return window.location.pathname || '/';
244
235
  }
245
- isQuotaError(error) {
246
- return error instanceof DOMException && (error.name === 'QuotaExceededError' || error.name === 'NS_ERROR_DOM_QUOTA_REACHED');
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);
247
244
  }
248
245
  messageFor(error, fallback) {
249
- return error instanceof Error ? error.message : fallback;
246
+ return error instanceof Error && error.message ? error.message : fallback;
250
247
  }
251
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: InspectorCheckpointRegistry, deps: [{ token: PLATFORM_ID }, { token: Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
252
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: InspectorCheckpointRegistry }); }
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 }); }
253
250
  }
254
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: InspectorCheckpointRegistry, decorators: [{
251
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: InspectorCheckpointService, decorators: [{
255
252
  type: Injectable
256
- }], ctorParameters: () => [{ type: undefined, decorators: [{
253
+ }], ctorParameters: () => [{ type: InspectorCheckpointRepository, decorators: [{
257
254
  type: Inject,
258
- args: [PLATFORM_ID]
259
- }] }, { type: i1.Router, decorators: [{
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
+ }, {
260
264
  type: Inject,
261
265
  args: [Router]
266
+ }] }, { type: undefined, decorators: [{
267
+ type: Inject,
268
+ args: [PLATFORM_ID]
262
269
  }] }] });
263
270
  function provideInspectorCheckpoints() {
264
- return [InspectorCheckpointRegistry];
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
+ ]);
265
284
  }
266
- function createBehaviorSubjectCheckpointAdapter(id, subject) {
267
- return {
268
- id,
269
- capture: () => subject.value,
270
- restore: (value) => subject.next(value),
271
- };
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;
272
328
  }
273
329
 
274
330
  const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
@@ -531,6 +587,56 @@ const getTargetElement = (point, overlayElement) => {
531
587
  return document.elementFromPoint(point.x, point.y);
532
588
  };
533
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
+
534
640
  const createId = () => typeof crypto !== 'undefined' && 'randomUUID' in crypto
535
641
  ? crypto.randomUUID()
536
642
  : `${Date.now()}-${Math.random()}`;
@@ -542,13 +648,22 @@ class inspectorComponent {
542
648
  this.formatValue = formatValue;
543
649
  this.guideHitboxSize = GUIDE_HITBOX_SIZE;
544
650
  this.overlayRoot = viewChild('overlayRoot');
651
+ this.checkpointDialog = viewChild('checkpointDialog');
652
+ this.checkpointSearch = viewChild('checkpointSearch');
545
653
  this.highlightColor = '#4f8cff';
546
654
  this.guideColor = '#ff7a00';
547
655
  this.hoverHighlightEnabled = true;
548
656
  this.persistOnReload = false;
549
- this.checkpointRegistry = inject(InspectorCheckpointRegistry, { optional: true });
550
- this.checkpointsOpen = signal(false);
657
+ this.checkpointService = inject(InspectorCheckpointService, { optional: true });
658
+ this.commandBarOpen = signal(false);
659
+ this.checkpointQuery = signal('');
660
+ this.activeCheckpointIndex = signal(0);
551
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()));
552
667
  this.enabled = signal(true);
553
668
  this.toolMode = signal('none');
554
669
  this.guideOrientation = signal('vertical');
@@ -584,6 +699,7 @@ class inspectorComponent {
584
699
  this.draggingGuideId = null;
585
700
  this.selectedElement = null;
586
701
  this.hoverElement = null;
702
+ this.toastTimer = null;
587
703
  this.canUndo = computed(() => this.historyIndex() > 0);
588
704
  this.canRedo = computed(() => this.historyIndex() >= 0 &&
589
705
  this.historyIndex() < this.history().length - 1);
@@ -647,6 +763,8 @@ class inspectorComponent {
647
763
  window.removeEventListener('click', this.handleCapturedClick, {
648
764
  capture: true,
649
765
  });
766
+ if (this.toastTimer)
767
+ clearTimeout(this.toastTimer);
650
768
  }
651
769
  setToolMode(mode) {
652
770
  if (!this.enabled()) {
@@ -673,31 +791,114 @@ class inspectorComponent {
673
791
  this.showTypography.update((value) => !value);
674
792
  this.refreshTypographyBlocks();
675
793
  }
676
- toggleCheckpoints() {
677
- this.checkpointsOpen.update((value) => !value);
678
- }
679
794
  disableInspector() {
680
795
  this.setInspectorEnabled(false);
681
796
  }
682
797
  async saveCheckpoint() {
683
- await this.checkpointRegistry?.save();
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
+ }
684
805
  }
685
- async restoreCheckpoint(checkpoint) {
686
- await this.checkpointRegistry?.restore(checkpoint.id);
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);
687
827
  }
688
- deleteCheckpoint(checkpoint) {
689
- this.checkpointRegistry?.delete(checkpoint.id);
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);
690
837
  }
691
- beginRename(checkpoint) {
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;
692
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
+ });
693
860
  }
694
- finishRename(checkpoint, event) {
695
- const input = event.target;
696
- this.checkpointRegistry?.rename(checkpoint.id, input.value);
697
- this.editingCheckpointId.set(null);
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
+ }
698
870
  }
699
871
  cancelRename() {
700
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
+ }
701
902
  }
702
903
  clearGuides() {
703
904
  this.guides.set([]);
@@ -718,6 +919,19 @@ class inspectorComponent {
718
919
  }
719
920
  handleKeydown(event) {
720
921
  const key = event.key.toLowerCase();
922
+ if (this.checkpointService &&
923
+ !event.repeat &&
924
+ (event.metaKey || event.ctrlKey) &&
925
+ event.shiftKey &&
926
+ key === 'p') {
927
+ event.preventDefault();
928
+ this.commandBarOpen() ? this.closeCheckpointCommandBar() : void this.openCheckpointCommandBar();
929
+ return;
930
+ }
931
+ if (this.commandBarOpen()) {
932
+ this.handleCommandBarKeydown(event, key);
933
+ return;
934
+ }
721
935
  if (key === 'm') {
722
936
  const target = event.target;
723
937
  if (target?.matches('input, textarea, select') ||
@@ -890,7 +1104,6 @@ class inspectorComponent {
890
1104
  this.distanceOverlay.set(null);
891
1105
  this.textBlocks.set([]);
892
1106
  this.guidePreview.set(null);
893
- this.checkpointsOpen.set(false);
894
1107
  this.altPressed.set(false);
895
1108
  this.selectedElement = null;
896
1109
  this.hoverElement = null;
@@ -898,6 +1111,77 @@ class inspectorComponent {
898
1111
  }
899
1112
  this.refreshTypographyBlocks();
900
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
+ }
901
1185
  selectElementAtPoint(event) {
902
1186
  const overlay = this.overlayRoot()?.nativeElement ?? null;
903
1187
  const target = getTargetElement({ x: event.clientX, y: event.clientY }, overlay);
@@ -1042,11 +1326,11 @@ class inspectorComponent {
1042
1326
  return null;
1043
1327
  }
1044
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 }); }
1045
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: inspectorComponent, isStandalone: true, selector: "inspector-overlay", inputs: { highlightColor: "highlightColor", guideColor: "guideColor", hoverHighlightEnabled: "hoverHighlightEnabled", persistOnReload: "persistOnReload" }, host: { listeners: { "window:keydown": "handleKeydown($event)", "window:keyup": "handleKeyup($event)", "window:pointermove": "handlePointerMove($event)", "window:pointerup": "handlePointerUp()", "window:click": "handleClick($event)", "window:resize": "handleViewportChange()", "window:scroll": "handleViewportChange()" } }, viewQueries: [{ propertyName: "overlayRoot", first: true, predicate: ["overlayRoot"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"inspector-root\" #overlayRoot>\n @if (enabled()) {\n <div class=\"inspector-toolbar\">\n <div class=\"inspector-toolbar__rail\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'select'\"\n (click)=\"setToolMode('select')\"\n aria-label=\"Select mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2l8 8-3 .7L6.7 13 6 10 3 2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Select</span>\n </button>\n\n @if (checkpointRegistry) {\n <div class=\"inspector-checkpoint-group\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"checkpointsOpen()\"\n (click)=\"toggleCheckpoints()\"\n aria-label=\"Checkpoints\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2.5h10v11H3zm2 2v2h6v-2zm0 4v2h4v-2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Checkpoints</span>\n </button>\n\n @if (checkpointsOpen()) {\n <section class=\"inspector-checkpoint-panel\" aria-label=\"Saved checkpoints\">\n <header class=\"inspector-checkpoint-panel__header\">\n <div>\n <strong>Checkpoints</strong>\n <span>{{ checkpointRegistry.checkpoints().length }}</span>\n </div>\n <button\n type=\"button\"\n class=\"inspector-checkpoint-save\"\n (click)=\"saveCheckpoint()\"\n [disabled]=\"checkpointRegistry.isBusy()\"\n >\n {{ checkpointRegistry.isBusy() ? 'Working\u2026' : 'Save current' }}\n </button>\n </header>\n\n @if (checkpointRegistry.error(); as error) {\n <p class=\"inspector-checkpoint-message inspector-checkpoint-message--error\">{{ error }}</p>\n }\n @if (checkpointRegistry.warning(); as warning) {\n <p class=\"inspector-checkpoint-message\">{{ warning }}</p>\n }\n\n @if (checkpointRegistry.checkpoints().length) {\n <div class=\"inspector-checkpoint-list\">\n @for (checkpoint of checkpointRegistry.checkpoints(); track checkpoint.id) {\n <article class=\"inspector-checkpoint-item\">\n <div class=\"inspector-checkpoint-item__main\">\n @if (editingCheckpointId() === checkpoint.id) {\n <input\n #checkpointName\n class=\"inspector-checkpoint-name-input\"\n [value]=\"checkpoint.name\"\n (keydown.enter)=\"finishRename(checkpoint, $event)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"finishRename(checkpoint, $event)\"\n aria-label=\"Checkpoint name\"\n autofocus\n />\n } @else {\n <button type=\"button\" class=\"inspector-checkpoint-name\" (click)=\"beginRename(checkpoint)\">\n {{ checkpoint.name }}\n </button>\n }\n </div>\n <div class=\"inspector-checkpoint-actions\">\n <button type=\"button\" (click)=\"restoreCheckpoint(checkpoint)\" [disabled]=\"checkpointRegistry.isBusy()\">Restore</button>\n <button type=\"button\" (click)=\"deleteCheckpoint(checkpoint)\" [disabled]=\"checkpointRegistry.isBusy()\">Delete</button>\n </div>\n </article>\n }\n </div>\n } @else {\n <p class=\"inspector-checkpoint-empty\">No checkpoints yet.</p>\n }\n </section>\n }\n </div>\n }\n\n <div class=\"inspector-toolbar__secondary\">\n <div class=\"inspector-toolbar__reveal\">\n <div class=\"inspector-toolbar__reveal-inner\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"showTypography()\"\n (click)=\"toggleTypography()\"\n aria-label=\"Typography overlay\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 4V2h10v2h-4v10H7V4z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Typography</span>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'guides' && guideOrientation() === 'vertical'\"\n (click)=\"activateGuideOrientation('vertical')\"\n aria-label=\"Vertical guides\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M7.25 2h1.5v12h-1.5z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Vertical guides</span>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'guides' && guideOrientation() === 'horizontal'\"\n (click)=\"activateGuideOrientation('horizontal')\"\n aria-label=\"Horizontal guides\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M2 7.25h12v1.5H2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Horizontal guides</span>\n </button>\n </div>\n </div>\n </div>\n\n <span class=\"inspector-toolbar__divider\" aria-hidden=\"true\"></span>\n <button\n type=\"button\"\n class=\"inspector-icon-button inspector-icon-button--power\"\n (click)=\"disableInspector()\"\n aria-label=\"Disable inspector; press M to enable\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M7.25 1.5h1.5v6h-1.5zM4.1 3.35l.96 1.15A4.25 4.25 0 1 0 10.94 4.5l.96-1.15A5.75 5.75 0 1 1 4.1 3.35z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Disable</span>\n </button>\n </div>\n </div>\n }\n\n @if (enabled() && toolMode() === \"select\") {\n @if (hoverRect() && hoverHighlightEnabled) {\n <div\n class=\"inspector-box inspector-box--hover\"\n [style.left.px]=\"hoverRect()!.left\"\n [style.top.px]=\"hoverRect()!.top\"\n [style.width.px]=\"hoverRect()!.width\"\n [style.height.px]=\"hoverRect()!.height\"\n [style.--inspector-accent]=\"highlightColor\"\n ></div>\n }\n\n @if (selectedMeasurement(); as selected) {\n @if (selected.gap.active && selected.parentRect) {\n <div\n class=\"inspector-box inspector-box--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top\"\n [style.width.px]=\"selected.parentRect.width\"\n [style.height.px]=\"selected.parentRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top - 8\"\n >\n G {{ formatGap(selected) }}\n </div>\n }\n\n <div\n class=\"inspector-box inspector-box--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top\"\n [style.width.px]=\"selected.marginRect.width\"\n [style.height.px]=\"selected.marginRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top - 8\"\n >\n M {{ formatEdges(selected.margin) }}\n </div>\n\n <div\n class=\"inspector-box inspector-box--selected\"\n [style.left.px]=\"selected.rect.left\"\n [style.top.px]=\"selected.rect.top\"\n [style.width.px]=\"selected.rect.width\"\n [style.height.px]=\"selected.rect.height\"\n [style.--inspector-accent]=\"highlightColor\"\n >\n <div class=\"inspector-label\">{{ selectedMetaLine() }}</div>\n </div>\n\n <div\n class=\"inspector-box inspector-box--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"selected.paddingRect.top\"\n [style.width.px]=\"selected.paddingRect.width\"\n [style.height.px]=\"selected.paddingRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"\n selected.paddingRect.top + selected.paddingRect.height + 8\n \"\n >\n P {{ formatEdges(selected.padding) }}\n </div>\n }\n }\n\n @if (enabled() && showTypography()) {\n @for (block of textBlocks(); track block.id) {\n <div\n class=\"inspector-text-chip\"\n [style.left.px]=\"block.rect.left\"\n [style.top.px]=\"block.rect.top - 8\"\n >\n <span\n class=\"inspector-text-chip__swatch\"\n [style.background]=\"block.styles.color\"\n ></span>\n <span>{{ block.styles.fontSize }}</span>\n <span>{{ block.styles.lineHeight }}</span>\n <span>{{ block.styles.color }}</span>\n </div>\n }\n }\n\n @if (enabled() && altPressed() && distanceOverlay(); as distance) {\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectA.left\"\n [style.top.px]=\"distance.rectA.top\"\n [style.width.px]=\"distance.rectA.width\"\n [style.height.px]=\"distance.rectA.height\"\n ></div>\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectB.left\"\n [style.top.px]=\"distance.rectB.top\"\n [style.width.px]=\"distance.rectB.width\"\n [style.height.px]=\"distance.rectB.height\"\n ></div>\n\n @for (\n connector of distance.connectors;\n track trackConnector($index, connector)\n ) {\n <div\n class=\"inspector-connector\"\n [class.inspector-connector--vertical]=\"connector.x1 === connector.x2\"\n [class.inspector-connector--horizontal]=\"connector.y1 === connector.y2\"\n [style.left.px]=\"Math.min(connector.x1, connector.x2)\"\n [style.top.px]=\"Math.min(connector.y1, connector.y2)\"\n [style.width.px]=\"Math.max(1, Math.abs(connector.x2 - connector.x1))\"\n [style.height.px]=\"Math.max(1, Math.abs(connector.y2 - connector.y1))\"\n ></div>\n }\n\n @if (distance.horizontal && distance.horizontal.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--horizontal\"\n [style.left.px]=\"\n Math.min(distance.horizontal.x1!, distance.horizontal.x2!)\n \"\n [style.top.px]=\"distance.horizontal.y!\"\n [style.width.px]=\"\n Math.abs(distance.horizontal.x2! - distance.horizontal.x1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag\"\n [style.left.px]=\"\n (distance.horizontal.x1! + distance.horizontal.x2!) / 2\n \"\n [style.top.px]=\"distance.horizontal.y! + 10\"\n >\n {{ formatValue(distance.horizontal.value) }}\n </div>\n }\n\n @if (distance.vertical && distance.vertical.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--vertical\"\n [style.left.px]=\"distance.vertical.x!\"\n [style.top.px]=\"Math.min(distance.vertical.y1!, distance.vertical.y2!)\"\n [style.height.px]=\"\n Math.abs(distance.vertical.y2! - distance.vertical.y1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag inspector-distance-tag--vertical\"\n [style.left.px]=\"distance.vertical.x! + 10\"\n [style.top.px]=\"(distance.vertical.y1! + distance.vertical.y2!) / 2\"\n >\n {{ formatValue(distance.vertical.value) }}\n </div>\n }\n }\n\n @if (enabled()) {\n @if (toolMode() === \"guides\") {\n @if (guidePreview(); as preview) {\n <div\n class=\"inspector-guide inspector-guide--preview\"\n [class.inspector-guide--vertical]=\"preview.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"\n preview.orientation === 'horizontal'\n \"\n [style.left.px]=\"\n preview.orientation === 'vertical' ? preview.position : null\n \"\n [style.top.px]=\"\n preview.orientation === 'horizontal' ? preview.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n >\n <span class=\"inspector-guide-line\"></span>\n </div>\n }\n }\n\n @for (guide of guides(); track guide.id) {\n <button\n type=\"button\"\n class=\"inspector-guide\"\n [class.inspector-guide--vertical]=\"guide.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"guide.orientation === 'horizontal'\"\n [class.inspector-guide--selected]=\"selectedGuideId() === guide.id\"\n [style.left.px]=\"\n guide.orientation === 'vertical' ? guide.position : null\n \"\n [style.top.px]=\"\n guide.orientation === 'horizontal' ? guide.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n (pointerdown)=\"startGuideDrag($event, guide)\"\n >\n <span class=\"inspector-guide-line\"></span>\n </button>\n }\n }\n</div>\n", styles: [":host{--inspector-panel: rgba(27, 29, 36, .94);--inspector-panel-edge: rgba(255, 255, 255, .08);--inspector-text: #f3f4f6;--inspector-muted: #9ca3af;--inspector-blue: #4f8cff;--inspector-orange: #f59e0b;--inspector-green: #22c55e;--inspector-violet: #a855f7;position:fixed;inset:0;z-index:2147;pointer-events:none;font-family:SF Pro Display,Segoe UI,sans-serif}.inspector-root{position:fixed;inset:0;pointer-events:none}.inspector-toolbar{position:fixed;top:12px;right:12px;display:block;pointer-events:auto;z-index:10000}.inspector-toolbar__rail{display:flex;flex-direction:column;align-items:center;gap:2px;width:30px;padding:2px;border-radius:4px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.inspector-toolbar__divider{width:16px;height:1px;margin-block:2px;background:var(--inspector-panel-edge)}.inspector-toolbar__secondary{display:grid;width:24px}.inspector-toolbar__reveal{display:grid;grid-template-rows:0fr;opacity:0;transform:translateY(-4px);transition:grid-template-rows .18s cubic-bezier(.16,1,.3,1),opacity .12s cubic-bezier(.16,1,.3,1),transform .18s cubic-bezier(.16,1,.3,1)}.inspector-toolbar__reveal-inner{display:grid;gap:2px;min-height:0;overflow:hidden}.inspector-toolbar__rail:hover .inspector-toolbar__reveal,.inspector-toolbar__rail:focus-within .inspector-toolbar__reveal{grid-template-rows:1fr;opacity:1;transform:translateY(2px)}.inspector-checkpoint-group{position:relative;width:24px;height:24px}.inspector-checkpoint-panel{position:absolute;top:0;right:calc(100% + 8px);width:min(300px,calc(100vw - 24px));max-height:min(420px,calc(100vh - 64px));overflow:auto;border:1px solid var(--inspector-panel-edge);border-radius:8px;background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;color:var(--inspector-text)}.inspector-checkpoint-panel__header,.inspector-checkpoint-item,.inspector-checkpoint-actions{display:flex;align-items:center}.inspector-checkpoint-panel__header{justify-content:space-between;gap:8px;padding:6px 8px;border-bottom:1px solid var(--inspector-panel-edge)}.inspector-checkpoint-panel__header strong,.inspector-checkpoint-panel__header span{display:inline}.inspector-checkpoint-panel__header strong{font-size:12px}.inspector-checkpoint-panel__header span{margin-left:5px}.inspector-checkpoint-panel__header span,.inspector-checkpoint-empty,.inspector-checkpoint-message{color:var(--inspector-muted);font-size:10px;line-height:1.4}.inspector-checkpoint-save,.inspector-checkpoint-actions button,.inspector-checkpoint-name{border:0;background:transparent;color:inherit;cursor:pointer;font:inherit}.inspector-checkpoint-save{padding:5px 7px;border-radius:5px;background:#4f8cff2e;color:#dbeafe;font-size:10px}.inspector-checkpoint-list{display:grid}.inspector-checkpoint-item{justify-content:space-between;gap:8px;padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.05)}.inspector-checkpoint-item__main{flex:1 1 auto;min-width:0}.inspector-checkpoint-name,.inspector-checkpoint-name-input{display:block;max-width:100%;overflow:hidden;padding:0;color:var(--inspector-text);font-size:11px;font-weight:600;text-align:left;text-overflow:ellipsis;white-space:nowrap}.inspector-checkpoint-name-input{width:100%;border:1px solid rgba(79,140,255,.6);border-radius:3px;background:#0003;outline:none}.inspector-checkpoint-actions{gap:2px;flex:0 0 auto}.inspector-checkpoint-actions button{padding:4px;border-radius:4px;color:var(--inspector-muted);font-size:10px}.inspector-checkpoint-actions button:hover,.inspector-checkpoint-name:hover,.inspector-checkpoint-save:hover{color:var(--inspector-text);background:#ffffff14}.inspector-checkpoint-empty,.inspector-checkpoint-message{margin:0;padding:12px 10px}.inspector-checkpoint-message--error{color:#fecaca}.inspector-icon-button{display:inline-flex;align-items:center;justify-content:center;height:24px;border:0;border-radius:3px;background:transparent;color:var(--inspector-muted);cursor:pointer;transition:background .1s cubic-bezier(.16,1,.3,1),color .1s cubic-bezier(.16,1,.3,1),transform .1s cubic-bezier(.16,1,.3,1);font:inherit;font-size:11px;letter-spacing:.02em}.inspector-icon-button{position:relative;width:24px}.inspector-icon-button svg{width:13px;height:13px;fill:currentColor}.inspector-icon-button:active{transform:translateY(1px)}.inspector-icon-button:focus-visible{outline:2px solid var(--inspector-blue);outline-offset:2px}.inspector-icon-button:hover{background:#ffffff14;color:var(--inspector-text)}.inspector-icon-button.is-active{background:#4f8cff2e;color:#dbeafe}.inspector-icon-button--power:hover{background:#ef444429;color:#fecaca}.inspector-tooltip{position:absolute;top:50%;right:calc(100% + 7px);z-index:20;width:max-content;padding:4px 6px;border:1px solid var(--inspector-panel-edge);border-radius:3px;background:#111318fa;color:var(--inspector-text);font-size:10px;font-weight:600;line-height:1.2;opacity:0;pointer-events:none;transform:translate(3px,-50%);transition:opacity .12s cubic-bezier(.16,1,.3,1),transform .12s cubic-bezier(.16,1,.3,1);white-space:nowrap}.inspector-icon-button:hover .inspector-tooltip,.inspector-icon-button:focus-visible .inspector-tooltip{opacity:1;transform:translateY(-50%)}@media (prefers-reduced-motion: reduce){.inspector-toolbar__reveal,.inspector-icon-button,.inspector-tooltip{transition-duration:.01ms}}.inspector-box{position:fixed;box-sizing:border-box;pointer-events:none;border:1px solid var(--inspector-accent, var(--inspector-blue));background:color-mix(in srgb,var(--inspector-accent, var(--inspector-blue)) 8%,transparent)}.inspector-box--hover{opacity:.55}.inspector-box--selected{border-width:1.5px}.inspector-box--margin{border-color:#f59e0bd1;background:#f59e0b14}.inspector-box--padding{border-color:#22c55ed1;background:#22c55e14}.inspector-box--gap{border-color:#a855f7b8;background:#a855f70a}.inspector-box--distance{border-style:dashed;border-color:#4f8cffb3;background:transparent}.inspector-label,.inspector-box-tag,.inspector-text-chip,.inspector-distance-tag{font-variant-numeric:tabular-nums}.inspector-label{position:absolute;left:0;top:0;transform:translateY(calc(-100% - 8px));max-width:min(420px,calc(100vw - 24px));padding:6px 9px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);color:var(--inspector-text);box-shadow:0 18px 40px #00000042;font-size:11px;line-height:1;white-space:nowrap}.inspector-box-tag{position:fixed;transform:translateY(-100%);padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:var(--inspector-text);box-shadow:0 12px 24px #0003;font-size:10px;line-height:1;pointer-events:none}.inspector-box-tag--margin{color:#fbbf24}.inspector-box-tag--padding{color:#86efac;transform:translateY(0)}.inspector-box-tag--gap{color:#d8b4fe}.inspector-text-chip{position:fixed;transform:translateY(-100%);display:inline-flex;gap:6px;align-items:center;padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:#e5e7eb;box-shadow:0 12px 24px #00000038;font-size:10px;line-height:1;white-space:nowrap;pointer-events:none}.inspector-text-chip__swatch{width:8px;height:8px;border-radius:999px;border:1px solid rgba(255,255,255,.18);flex:0 0 auto}.inspector-connector{position:fixed;border-color:#4f8cffb8;border-style:dashed;pointer-events:none}.inspector-connector--vertical{border-left-width:1px;width:0}.inspector-connector--horizontal{border-top-width:1px;height:0}.inspector-distance-line{position:fixed;background:var(--inspector-blue);pointer-events:none}.inspector-distance-line--horizontal{height:1px}.inspector-distance-line--vertical{width:1px}.inspector-distance-tag{position:fixed;transform:translate(-50%);padding:5px 7px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f2;color:var(--inspector-text);font-size:10px;line-height:1;pointer-events:none}.inspector-distance-tag--vertical{transform:translateY(-50%)}.inspector-guide{position:fixed;margin:0;padding:0;border:0;background:transparent;pointer-events:auto;cursor:grab}.inspector-guide--vertical{top:0;width:14px;height:100vh;transform:translate(-50%)}.inspector-guide--horizontal{left:0;width:100vw;height:14px;transform:translateY(-50%)}.inspector-guide--preview{opacity:.55;pointer-events:none;cursor:default}.inspector-guide--preview .inspector-guide-line{background:color-mix(in srgb,var(--inspector-guide, #ff7a00) 72%,transparent)}.inspector-guide-line{position:absolute;inset:0;pointer-events:none}.inspector-guide--vertical .inspector-guide-line{left:calc(50% - .5px);width:1px;height:100%;background:var(--inspector-guide, #ff7a00)}.inspector-guide--horizontal .inspector-guide-line{top:calc(50% - .5px);width:100%;height:1px;background:var(--inspector-guide, #ff7a00)}.inspector-guide--selected .inspector-guide-line{box-shadow:0 0 0 1px #ffffff80}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
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 }); }
1046
1330
  }
1047
1331
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: inspectorComponent, decorators: [{
1048
1332
  type: Component,
1049
- args: [{ selector: 'inspector-overlay', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"inspector-root\" #overlayRoot>\n @if (enabled()) {\n <div class=\"inspector-toolbar\">\n <div class=\"inspector-toolbar__rail\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'select'\"\n (click)=\"setToolMode('select')\"\n aria-label=\"Select mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2l8 8-3 .7L6.7 13 6 10 3 2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Select</span>\n </button>\n\n @if (checkpointRegistry) {\n <div class=\"inspector-checkpoint-group\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"checkpointsOpen()\"\n (click)=\"toggleCheckpoints()\"\n aria-label=\"Checkpoints\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2.5h10v11H3zm2 2v2h6v-2zm0 4v2h4v-2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Checkpoints</span>\n </button>\n\n @if (checkpointsOpen()) {\n <section class=\"inspector-checkpoint-panel\" aria-label=\"Saved checkpoints\">\n <header class=\"inspector-checkpoint-panel__header\">\n <div>\n <strong>Checkpoints</strong>\n <span>{{ checkpointRegistry.checkpoints().length }}</span>\n </div>\n <button\n type=\"button\"\n class=\"inspector-checkpoint-save\"\n (click)=\"saveCheckpoint()\"\n [disabled]=\"checkpointRegistry.isBusy()\"\n >\n {{ checkpointRegistry.isBusy() ? 'Working\u2026' : 'Save current' }}\n </button>\n </header>\n\n @if (checkpointRegistry.error(); as error) {\n <p class=\"inspector-checkpoint-message inspector-checkpoint-message--error\">{{ error }}</p>\n }\n @if (checkpointRegistry.warning(); as warning) {\n <p class=\"inspector-checkpoint-message\">{{ warning }}</p>\n }\n\n @if (checkpointRegistry.checkpoints().length) {\n <div class=\"inspector-checkpoint-list\">\n @for (checkpoint of checkpointRegistry.checkpoints(); track checkpoint.id) {\n <article class=\"inspector-checkpoint-item\">\n <div class=\"inspector-checkpoint-item__main\">\n @if (editingCheckpointId() === checkpoint.id) {\n <input\n #checkpointName\n class=\"inspector-checkpoint-name-input\"\n [value]=\"checkpoint.name\"\n (keydown.enter)=\"finishRename(checkpoint, $event)\"\n (keydown.escape)=\"cancelRename()\"\n (blur)=\"finishRename(checkpoint, $event)\"\n aria-label=\"Checkpoint name\"\n autofocus\n />\n } @else {\n <button type=\"button\" class=\"inspector-checkpoint-name\" (click)=\"beginRename(checkpoint)\">\n {{ checkpoint.name }}\n </button>\n }\n </div>\n <div class=\"inspector-checkpoint-actions\">\n <button type=\"button\" (click)=\"restoreCheckpoint(checkpoint)\" [disabled]=\"checkpointRegistry.isBusy()\">Restore</button>\n <button type=\"button\" (click)=\"deleteCheckpoint(checkpoint)\" [disabled]=\"checkpointRegistry.isBusy()\">Delete</button>\n </div>\n </article>\n }\n </div>\n } @else {\n <p class=\"inspector-checkpoint-empty\">No checkpoints yet.</p>\n }\n </section>\n }\n </div>\n }\n\n <div class=\"inspector-toolbar__secondary\">\n <div class=\"inspector-toolbar__reveal\">\n <div class=\"inspector-toolbar__reveal-inner\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"showTypography()\"\n (click)=\"toggleTypography()\"\n aria-label=\"Typography overlay\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 4V2h10v2h-4v10H7V4z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Typography</span>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'guides' && guideOrientation() === 'vertical'\"\n (click)=\"activateGuideOrientation('vertical')\"\n aria-label=\"Vertical guides\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M7.25 2h1.5v12h-1.5z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Vertical guides</span>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'guides' && guideOrientation() === 'horizontal'\"\n (click)=\"activateGuideOrientation('horizontal')\"\n aria-label=\"Horizontal guides\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M2 7.25h12v1.5H2z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Horizontal guides</span>\n </button>\n </div>\n </div>\n </div>\n\n <span class=\"inspector-toolbar__divider\" aria-hidden=\"true\"></span>\n <button\n type=\"button\"\n class=\"inspector-icon-button inspector-icon-button--power\"\n (click)=\"disableInspector()\"\n aria-label=\"Disable inspector; press M to enable\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M7.25 1.5h1.5v6h-1.5zM4.1 3.35l.96 1.15A4.25 4.25 0 1 0 10.94 4.5l.96-1.15A5.75 5.75 0 1 1 4.1 3.35z\" />\n </svg>\n <span class=\"inspector-tooltip\" role=\"tooltip\">Disable</span>\n </button>\n </div>\n </div>\n }\n\n @if (enabled() && toolMode() === \"select\") {\n @if (hoverRect() && hoverHighlightEnabled) {\n <div\n class=\"inspector-box inspector-box--hover\"\n [style.left.px]=\"hoverRect()!.left\"\n [style.top.px]=\"hoverRect()!.top\"\n [style.width.px]=\"hoverRect()!.width\"\n [style.height.px]=\"hoverRect()!.height\"\n [style.--inspector-accent]=\"highlightColor\"\n ></div>\n }\n\n @if (selectedMeasurement(); as selected) {\n @if (selected.gap.active && selected.parentRect) {\n <div\n class=\"inspector-box inspector-box--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top\"\n [style.width.px]=\"selected.parentRect.width\"\n [style.height.px]=\"selected.parentRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top - 8\"\n >\n G {{ formatGap(selected) }}\n </div>\n }\n\n <div\n class=\"inspector-box inspector-box--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top\"\n [style.width.px]=\"selected.marginRect.width\"\n [style.height.px]=\"selected.marginRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top - 8\"\n >\n M {{ formatEdges(selected.margin) }}\n </div>\n\n <div\n class=\"inspector-box inspector-box--selected\"\n [style.left.px]=\"selected.rect.left\"\n [style.top.px]=\"selected.rect.top\"\n [style.width.px]=\"selected.rect.width\"\n [style.height.px]=\"selected.rect.height\"\n [style.--inspector-accent]=\"highlightColor\"\n >\n <div class=\"inspector-label\">{{ selectedMetaLine() }}</div>\n </div>\n\n <div\n class=\"inspector-box inspector-box--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"selected.paddingRect.top\"\n [style.width.px]=\"selected.paddingRect.width\"\n [style.height.px]=\"selected.paddingRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"\n selected.paddingRect.top + selected.paddingRect.height + 8\n \"\n >\n P {{ formatEdges(selected.padding) }}\n </div>\n }\n }\n\n @if (enabled() && showTypography()) {\n @for (block of textBlocks(); track block.id) {\n <div\n class=\"inspector-text-chip\"\n [style.left.px]=\"block.rect.left\"\n [style.top.px]=\"block.rect.top - 8\"\n >\n <span\n class=\"inspector-text-chip__swatch\"\n [style.background]=\"block.styles.color\"\n ></span>\n <span>{{ block.styles.fontSize }}</span>\n <span>{{ block.styles.lineHeight }}</span>\n <span>{{ block.styles.color }}</span>\n </div>\n }\n }\n\n @if (enabled() && altPressed() && distanceOverlay(); as distance) {\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectA.left\"\n [style.top.px]=\"distance.rectA.top\"\n [style.width.px]=\"distance.rectA.width\"\n [style.height.px]=\"distance.rectA.height\"\n ></div>\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectB.left\"\n [style.top.px]=\"distance.rectB.top\"\n [style.width.px]=\"distance.rectB.width\"\n [style.height.px]=\"distance.rectB.height\"\n ></div>\n\n @for (\n connector of distance.connectors;\n track trackConnector($index, connector)\n ) {\n <div\n class=\"inspector-connector\"\n [class.inspector-connector--vertical]=\"connector.x1 === connector.x2\"\n [class.inspector-connector--horizontal]=\"connector.y1 === connector.y2\"\n [style.left.px]=\"Math.min(connector.x1, connector.x2)\"\n [style.top.px]=\"Math.min(connector.y1, connector.y2)\"\n [style.width.px]=\"Math.max(1, Math.abs(connector.x2 - connector.x1))\"\n [style.height.px]=\"Math.max(1, Math.abs(connector.y2 - connector.y1))\"\n ></div>\n }\n\n @if (distance.horizontal && distance.horizontal.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--horizontal\"\n [style.left.px]=\"\n Math.min(distance.horizontal.x1!, distance.horizontal.x2!)\n \"\n [style.top.px]=\"distance.horizontal.y!\"\n [style.width.px]=\"\n Math.abs(distance.horizontal.x2! - distance.horizontal.x1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag\"\n [style.left.px]=\"\n (distance.horizontal.x1! + distance.horizontal.x2!) / 2\n \"\n [style.top.px]=\"distance.horizontal.y! + 10\"\n >\n {{ formatValue(distance.horizontal.value) }}\n </div>\n }\n\n @if (distance.vertical && distance.vertical.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--vertical\"\n [style.left.px]=\"distance.vertical.x!\"\n [style.top.px]=\"Math.min(distance.vertical.y1!, distance.vertical.y2!)\"\n [style.height.px]=\"\n Math.abs(distance.vertical.y2! - distance.vertical.y1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag inspector-distance-tag--vertical\"\n [style.left.px]=\"distance.vertical.x! + 10\"\n [style.top.px]=\"(distance.vertical.y1! + distance.vertical.y2!) / 2\"\n >\n {{ formatValue(distance.vertical.value) }}\n </div>\n }\n }\n\n @if (enabled()) {\n @if (toolMode() === \"guides\") {\n @if (guidePreview(); as preview) {\n <div\n class=\"inspector-guide inspector-guide--preview\"\n [class.inspector-guide--vertical]=\"preview.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"\n preview.orientation === 'horizontal'\n \"\n [style.left.px]=\"\n preview.orientation === 'vertical' ? preview.position : null\n \"\n [style.top.px]=\"\n preview.orientation === 'horizontal' ? preview.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n >\n <span class=\"inspector-guide-line\"></span>\n </div>\n }\n }\n\n @for (guide of guides(); track guide.id) {\n <button\n type=\"button\"\n class=\"inspector-guide\"\n [class.inspector-guide--vertical]=\"guide.orientation === 'vertical'\"\n [class.inspector-guide--horizontal]=\"guide.orientation === 'horizontal'\"\n [class.inspector-guide--selected]=\"selectedGuideId() === guide.id\"\n [style.left.px]=\"\n guide.orientation === 'vertical' ? guide.position : null\n \"\n [style.top.px]=\"\n guide.orientation === 'horizontal' ? guide.position : null\n \"\n [style.--inspector-guide]=\"guideColor\"\n (pointerdown)=\"startGuideDrag($event, guide)\"\n >\n <span class=\"inspector-guide-line\"></span>\n </button>\n }\n }\n</div>\n", styles: [":host{--inspector-panel: rgba(27, 29, 36, .94);--inspector-panel-edge: rgba(255, 255, 255, .08);--inspector-text: #f3f4f6;--inspector-muted: #9ca3af;--inspector-blue: #4f8cff;--inspector-orange: #f59e0b;--inspector-green: #22c55e;--inspector-violet: #a855f7;position:fixed;inset:0;z-index:2147;pointer-events:none;font-family:SF Pro Display,Segoe UI,sans-serif}.inspector-root{position:fixed;inset:0;pointer-events:none}.inspector-toolbar{position:fixed;top:12px;right:12px;display:block;pointer-events:auto;z-index:10000}.inspector-toolbar__rail{display:flex;flex-direction:column;align-items:center;gap:2px;width:30px;padding:2px;border-radius:4px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.inspector-toolbar__divider{width:16px;height:1px;margin-block:2px;background:var(--inspector-panel-edge)}.inspector-toolbar__secondary{display:grid;width:24px}.inspector-toolbar__reveal{display:grid;grid-template-rows:0fr;opacity:0;transform:translateY(-4px);transition:grid-template-rows .18s cubic-bezier(.16,1,.3,1),opacity .12s cubic-bezier(.16,1,.3,1),transform .18s cubic-bezier(.16,1,.3,1)}.inspector-toolbar__reveal-inner{display:grid;gap:2px;min-height:0;overflow:hidden}.inspector-toolbar__rail:hover .inspector-toolbar__reveal,.inspector-toolbar__rail:focus-within .inspector-toolbar__reveal{grid-template-rows:1fr;opacity:1;transform:translateY(2px)}.inspector-checkpoint-group{position:relative;width:24px;height:24px}.inspector-checkpoint-panel{position:absolute;top:0;right:calc(100% + 8px);width:min(300px,calc(100vw - 24px));max-height:min(420px,calc(100vh - 64px));overflow:auto;border:1px solid var(--inspector-panel-edge);border-radius:8px;background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;color:var(--inspector-text)}.inspector-checkpoint-panel__header,.inspector-checkpoint-item,.inspector-checkpoint-actions{display:flex;align-items:center}.inspector-checkpoint-panel__header{justify-content:space-between;gap:8px;padding:6px 8px;border-bottom:1px solid var(--inspector-panel-edge)}.inspector-checkpoint-panel__header strong,.inspector-checkpoint-panel__header span{display:inline}.inspector-checkpoint-panel__header strong{font-size:12px}.inspector-checkpoint-panel__header span{margin-left:5px}.inspector-checkpoint-panel__header span,.inspector-checkpoint-empty,.inspector-checkpoint-message{color:var(--inspector-muted);font-size:10px;line-height:1.4}.inspector-checkpoint-save,.inspector-checkpoint-actions button,.inspector-checkpoint-name{border:0;background:transparent;color:inherit;cursor:pointer;font:inherit}.inspector-checkpoint-save{padding:5px 7px;border-radius:5px;background:#4f8cff2e;color:#dbeafe;font-size:10px}.inspector-checkpoint-list{display:grid}.inspector-checkpoint-item{justify-content:space-between;gap:8px;padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.05)}.inspector-checkpoint-item__main{flex:1 1 auto;min-width:0}.inspector-checkpoint-name,.inspector-checkpoint-name-input{display:block;max-width:100%;overflow:hidden;padding:0;color:var(--inspector-text);font-size:11px;font-weight:600;text-align:left;text-overflow:ellipsis;white-space:nowrap}.inspector-checkpoint-name-input{width:100%;border:1px solid rgba(79,140,255,.6);border-radius:3px;background:#0003;outline:none}.inspector-checkpoint-actions{gap:2px;flex:0 0 auto}.inspector-checkpoint-actions button{padding:4px;border-radius:4px;color:var(--inspector-muted);font-size:10px}.inspector-checkpoint-actions button:hover,.inspector-checkpoint-name:hover,.inspector-checkpoint-save:hover{color:var(--inspector-text);background:#ffffff14}.inspector-checkpoint-empty,.inspector-checkpoint-message{margin:0;padding:12px 10px}.inspector-checkpoint-message--error{color:#fecaca}.inspector-icon-button{display:inline-flex;align-items:center;justify-content:center;height:24px;border:0;border-radius:3px;background:transparent;color:var(--inspector-muted);cursor:pointer;transition:background .1s cubic-bezier(.16,1,.3,1),color .1s cubic-bezier(.16,1,.3,1),transform .1s cubic-bezier(.16,1,.3,1);font:inherit;font-size:11px;letter-spacing:.02em}.inspector-icon-button{position:relative;width:24px}.inspector-icon-button svg{width:13px;height:13px;fill:currentColor}.inspector-icon-button:active{transform:translateY(1px)}.inspector-icon-button:focus-visible{outline:2px solid var(--inspector-blue);outline-offset:2px}.inspector-icon-button:hover{background:#ffffff14;color:var(--inspector-text)}.inspector-icon-button.is-active{background:#4f8cff2e;color:#dbeafe}.inspector-icon-button--power:hover{background:#ef444429;color:#fecaca}.inspector-tooltip{position:absolute;top:50%;right:calc(100% + 7px);z-index:20;width:max-content;padding:4px 6px;border:1px solid var(--inspector-panel-edge);border-radius:3px;background:#111318fa;color:var(--inspector-text);font-size:10px;font-weight:600;line-height:1.2;opacity:0;pointer-events:none;transform:translate(3px,-50%);transition:opacity .12s cubic-bezier(.16,1,.3,1),transform .12s cubic-bezier(.16,1,.3,1);white-space:nowrap}.inspector-icon-button:hover .inspector-tooltip,.inspector-icon-button:focus-visible .inspector-tooltip{opacity:1;transform:translateY(-50%)}@media (prefers-reduced-motion: reduce){.inspector-toolbar__reveal,.inspector-icon-button,.inspector-tooltip{transition-duration:.01ms}}.inspector-box{position:fixed;box-sizing:border-box;pointer-events:none;border:1px solid var(--inspector-accent, var(--inspector-blue));background:color-mix(in srgb,var(--inspector-accent, var(--inspector-blue)) 8%,transparent)}.inspector-box--hover{opacity:.55}.inspector-box--selected{border-width:1.5px}.inspector-box--margin{border-color:#f59e0bd1;background:#f59e0b14}.inspector-box--padding{border-color:#22c55ed1;background:#22c55e14}.inspector-box--gap{border-color:#a855f7b8;background:#a855f70a}.inspector-box--distance{border-style:dashed;border-color:#4f8cffb3;background:transparent}.inspector-label,.inspector-box-tag,.inspector-text-chip,.inspector-distance-tag{font-variant-numeric:tabular-nums}.inspector-label{position:absolute;left:0;top:0;transform:translateY(calc(-100% - 8px));max-width:min(420px,calc(100vw - 24px));padding:6px 9px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);color:var(--inspector-text);box-shadow:0 18px 40px #00000042;font-size:11px;line-height:1;white-space:nowrap}.inspector-box-tag{position:fixed;transform:translateY(-100%);padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:var(--inspector-text);box-shadow:0 12px 24px #0003;font-size:10px;line-height:1;pointer-events:none}.inspector-box-tag--margin{color:#fbbf24}.inspector-box-tag--padding{color:#86efac;transform:translateY(0)}.inspector-box-tag--gap{color:#d8b4fe}.inspector-text-chip{position:fixed;transform:translateY(-100%);display:inline-flex;gap:6px;align-items:center;padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:#e5e7eb;box-shadow:0 12px 24px #00000038;font-size:10px;line-height:1;white-space:nowrap;pointer-events:none}.inspector-text-chip__swatch{width:8px;height:8px;border-radius:999px;border:1px solid rgba(255,255,255,.18);flex:0 0 auto}.inspector-connector{position:fixed;border-color:#4f8cffb8;border-style:dashed;pointer-events:none}.inspector-connector--vertical{border-left-width:1px;width:0}.inspector-connector--horizontal{border-top-width:1px;height:0}.inspector-distance-line{position:fixed;background:var(--inspector-blue);pointer-events:none}.inspector-distance-line--horizontal{height:1px}.inspector-distance-line--vertical{width:1px}.inspector-distance-tag{position:fixed;transform:translate(-50%);padding:5px 7px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f2;color:var(--inspector-text);font-size:10px;line-height:1;pointer-events:none}.inspector-distance-tag--vertical{transform:translateY(-50%)}.inspector-guide{position:fixed;margin:0;padding:0;border:0;background:transparent;pointer-events:auto;cursor:grab}.inspector-guide--vertical{top:0;width:14px;height:100vh;transform:translate(-50%)}.inspector-guide--horizontal{left:0;width:100vw;height:14px;transform:translateY(-50%)}.inspector-guide--preview{opacity:.55;pointer-events:none;cursor:default}.inspector-guide--preview .inspector-guide-line{background:color-mix(in srgb,var(--inspector-guide, #ff7a00) 72%,transparent)}.inspector-guide-line{position:absolute;inset:0;pointer-events:none}.inspector-guide--vertical .inspector-guide-line{left:calc(50% - .5px);width:1px;height:100%;background:var(--inspector-guide, #ff7a00)}.inspector-guide--horizontal .inspector-guide-line{top:calc(50% - .5px);width:100%;height:1px;background:var(--inspector-guide, #ff7a00)}.inspector-guide--selected .inspector-guide-line{box-shadow:0 0 0 1px #ffffff80}\n"] }]
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"] }]
1050
1334
  }], ctorParameters: () => [{ type: undefined, decorators: [{
1051
1335
  type: Inject,
1052
1336
  args: [PLATFORM_ID]
@@ -1085,5 +1369,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1085
1369
  * Generated bundle index. Do not edit.
1086
1370
  */
1087
1371
 
1088
- export { GUIDE_HITBOX_SIZE, GUIDE_SNAP_DISTANCE, InspectorCheckpointRegistry, clamp, createBehaviorSubjectCheckpointAdapter, createId, formatValue, getDistanceOverlay, getElementLabel, getInspectMeasurement, getRectFromDom, getTargetElement, getTextInspection, getViewportSize, getVisibleTextBlocks, inspectorComponent, inspector_STATE_VERSION, inspector_STORAGE_KEY, provideInspectorCheckpoints, rectContainsPoint, rectsEqual };
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 };
1089
1373
  //# sourceMappingURL=inspector-ng.mjs.map