inspector-ng 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,680 @@
1
+ import * as i0 from '@angular/core';
2
+ import { viewChild, signal, computed, effect, PLATFORM_ID, HostListener, Input, Inject, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { isPlatformBrowser } from '@angular/common';
4
+
5
+ const inspector_STORAGE_KEY = "inspector-state";
6
+ const inspector_STATE_VERSION = 1;
7
+ const GUIDE_SNAP_DISTANCE = 10;
8
+ const GUIDE_HITBOX_SIZE = 14;
9
+
10
+ const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
11
+ const getViewportSize = () => ({
12
+ width: typeof window === 'undefined' ? 1 : window.innerWidth || 1,
13
+ height: typeof window === 'undefined' ? 1 : window.innerHeight || 1,
14
+ });
15
+ const rectContainsPoint = (rect, point) => point.x >= rect.left &&
16
+ point.x <= rect.left + rect.width &&
17
+ point.y >= rect.top &&
18
+ point.y <= rect.top + rect.height;
19
+ const rectsEqual = (a, b, epsilon = 0.5) => {
20
+ if (a === b) {
21
+ return true;
22
+ }
23
+ if (!a || !b) {
24
+ return false;
25
+ }
26
+ return (Math.abs(a.left - b.left) < epsilon &&
27
+ Math.abs(a.top - b.top) < epsilon &&
28
+ Math.abs(a.width - b.width) < epsilon &&
29
+ Math.abs(a.height - b.height) < epsilon);
30
+ };
31
+
32
+ const getDistanceOverlay = (rectA, rectB) => {
33
+ const rightA = rectA.left + rectA.width;
34
+ const bottomA = rectA.top + rectA.height;
35
+ const rightB = rectB.left + rectB.width;
36
+ const bottomB = rectB.top + rectB.height;
37
+ const centerAX = rectA.left + rectA.width / 2;
38
+ const centerAY = rectA.top + rectA.height / 2;
39
+ let horizontal = null;
40
+ let vertical = null;
41
+ const connectors = [];
42
+ const separatedX = rightA <= rectB.left || rightB <= rectA.left;
43
+ const separatedY = bottomA <= rectB.top || bottomB <= rectA.top;
44
+ if (separatedX) {
45
+ const aIsLeft = rightA <= rectB.left;
46
+ const x1 = aIsLeft ? rightA : rightB;
47
+ const x2 = aIsLeft ? rectB.left : rectA.left;
48
+ const y = centerAY;
49
+ horizontal = { x1, x2, y, value: Math.abs(x2 - x1) };
50
+ const edgeBX = aIsLeft ? rectB.left : rightB;
51
+ if (y < rectB.top) {
52
+ connectors.push({ x1: edgeBX, y1: y, x2: edgeBX, y2: rectB.top });
53
+ }
54
+ else if (y > bottomB) {
55
+ connectors.push({ x1: edgeBX, y1: y, x2: edgeBX, y2: bottomB });
56
+ }
57
+ }
58
+ if (separatedY) {
59
+ const aIsTop = bottomA <= rectB.top;
60
+ const y1 = aIsTop ? bottomA : bottomB;
61
+ const y2 = aIsTop ? rectB.top : rectA.top;
62
+ const x = centerAX;
63
+ vertical = { y1, y2, x, value: Math.abs(y2 - y1) };
64
+ const edgeBY = aIsTop ? rectB.top : bottomB;
65
+ if (x < rectB.left) {
66
+ connectors.push({ x1: x, y1: edgeBY, x2: rectB.left, y2: edgeBY });
67
+ }
68
+ else if (x > rightB) {
69
+ connectors.push({ x1: x, y1: edgeBY, x2: rightB, y2: edgeBY });
70
+ }
71
+ }
72
+ const viewport = getViewportSize();
73
+ return {
74
+ rectA,
75
+ rectB,
76
+ horizontal,
77
+ vertical,
78
+ connectors: connectors
79
+ .map((segment) => ({
80
+ x1: clamp(segment.x1, 0, viewport.width),
81
+ y1: clamp(segment.y1, 0, viewport.height),
82
+ x2: clamp(segment.x2, 0, viewport.width),
83
+ y2: clamp(segment.y2, 0, viewport.height),
84
+ }))
85
+ .filter((segment) => Math.abs(segment.x1 - segment.x2) > 0.5 ||
86
+ Math.abs(segment.y1 - segment.y2) > 0.5),
87
+ };
88
+ };
89
+
90
+ const getRectFromDom = (element) => {
91
+ const rect = element.getBoundingClientRect();
92
+ return {
93
+ left: rect.left,
94
+ top: rect.top,
95
+ width: rect.width,
96
+ height: rect.height,
97
+ };
98
+ };
99
+ const getElementLabel = (element) => {
100
+ const tag = element.tagName.toLowerCase();
101
+ const id = element.id ? `#${element.id}` : '';
102
+ const className = element.className
103
+ ? `.${element.className.toString().split(' ')[0]}`
104
+ : '';
105
+ return `${tag}${id}${className}`;
106
+ };
107
+ const toHex = (value) => value.toString(16).padStart(2, '0');
108
+ const normalizeColorToHex = (color) => {
109
+ const trimmed = color.trim().toLowerCase();
110
+ if (trimmed.startsWith('#')) {
111
+ if (trimmed.length === 4) {
112
+ return `#${trimmed[1]}${trimmed[1]}${trimmed[2]}${trimmed[2]}${trimmed[3]}${trimmed[3]}`;
113
+ }
114
+ return trimmed;
115
+ }
116
+ const rgbMatch = trimmed.match(/^rgba?\(\s*([0-9.]+)\s*,\s*([0-9.]+)\s*,\s*([0-9.]+)(?:\s*,\s*[0-9.]+\s*)?\)$/);
117
+ if (!rgbMatch) {
118
+ return color;
119
+ }
120
+ const [, r, g, b] = rgbMatch;
121
+ return `#${toHex(Math.round(Number(r)))}${toHex(Math.round(Number(g)))}${toHex(Math.round(Number(b)))}`;
122
+ };
123
+ const getTextInspection = (element) => {
124
+ const style = window.getComputedStyle(element);
125
+ return {
126
+ fontFamily: style.fontFamily,
127
+ fontSize: style.fontSize,
128
+ fontWeight: style.fontWeight,
129
+ lineHeight: style.lineHeight,
130
+ letterSpacing: style.letterSpacing,
131
+ color: normalizeColorToHex(style.color),
132
+ textAlign: style.textAlign,
133
+ };
134
+ };
135
+ const parseEdge = (value) => Number.parseFloat(value) || 0;
136
+ const getInspectMeasurement = (element) => {
137
+ const rect = getRectFromDom(element);
138
+ const style = window.getComputedStyle(element);
139
+ const parentElement = element.parentElement;
140
+ const parentStyle = parentElement ? window.getComputedStyle(parentElement) : null;
141
+ const padding = {
142
+ top: parseEdge(style.paddingTop),
143
+ right: parseEdge(style.paddingRight),
144
+ bottom: parseEdge(style.paddingBottom),
145
+ left: parseEdge(style.paddingLeft),
146
+ };
147
+ const margin = {
148
+ top: parseEdge(style.marginTop),
149
+ right: parseEdge(style.marginRight),
150
+ bottom: parseEdge(style.marginBottom),
151
+ left: parseEdge(style.marginLeft),
152
+ };
153
+ const rowGap = parentStyle ? parseEdge(parentStyle.rowGap) : 0;
154
+ const columnGap = parentStyle ? parseEdge(parentStyle.columnGap) : 0;
155
+ const parentDisplay = parentStyle?.display ?? '';
156
+ const hasGap = !!parentElement &&
157
+ (parentDisplay.includes('flex') || parentDisplay.includes('grid')) &&
158
+ (rowGap > 0 || columnGap > 0);
159
+ return {
160
+ rect,
161
+ paddingRect: {
162
+ left: rect.left + padding.left,
163
+ top: rect.top + padding.top,
164
+ width: Math.max(0, rect.width - padding.left - padding.right),
165
+ height: Math.max(0, rect.height - padding.top - padding.bottom),
166
+ },
167
+ marginRect: {
168
+ left: rect.left - margin.left,
169
+ top: rect.top - margin.top,
170
+ width: rect.width + margin.left + margin.right,
171
+ height: rect.height + margin.top + margin.bottom,
172
+ },
173
+ padding,
174
+ margin,
175
+ gap: {
176
+ row: rowGap,
177
+ column: columnGap,
178
+ active: hasGap,
179
+ },
180
+ parentRect: hasGap && parentElement ? getRectFromDom(parentElement) : null,
181
+ label: getElementLabel(element),
182
+ styles: getTextInspection(element),
183
+ };
184
+ };
185
+ const isVisibleTextCandidate = (element) => {
186
+ const text = element.innerText?.trim() ?? '';
187
+ if (!text) {
188
+ return false;
189
+ }
190
+ const style = window.getComputedStyle(element);
191
+ if (style.display === 'none' ||
192
+ style.visibility === 'hidden' ||
193
+ Number.parseFloat(style.opacity || '1') === 0) {
194
+ return false;
195
+ }
196
+ const rect = element.getBoundingClientRect();
197
+ if (rect.width < 8 || rect.height < 8) {
198
+ return false;
199
+ }
200
+ for (const child of Array.from(element.children)) {
201
+ if (!(child instanceof HTMLElement)) {
202
+ continue;
203
+ }
204
+ if ((child.innerText?.trim() ?? '') && child.getBoundingClientRect().height > 0) {
205
+ return false;
206
+ }
207
+ }
208
+ return true;
209
+ };
210
+ const getVisibleTextBlocks = (overlayElement, limit = 160) => {
211
+ if (typeof document === 'undefined') {
212
+ return [];
213
+ }
214
+ const nodes = Array.from(document.querySelectorAll('body *'));
215
+ const blocks = [];
216
+ for (const node of nodes) {
217
+ if (!(node instanceof HTMLElement)) {
218
+ continue;
219
+ }
220
+ if (overlayElement && overlayElement.contains(node)) {
221
+ continue;
222
+ }
223
+ if (!isVisibleTextCandidate(node)) {
224
+ continue;
225
+ }
226
+ const rect = getRectFromDom(node);
227
+ const text = (node.innerText || node.textContent || '').replace(/\s+/g, ' ').trim();
228
+ blocks.push({
229
+ id: `${node.tagName.toLowerCase()}-${Math.round(rect.left)}-${Math.round(rect.top)}-${text.slice(0, 24)}`,
230
+ rect,
231
+ text,
232
+ styles: getTextInspection(node),
233
+ });
234
+ if (blocks.length >= limit) {
235
+ break;
236
+ }
237
+ }
238
+ return blocks;
239
+ };
240
+ const getTargetElement = (point, overlayElement) => {
241
+ if (typeof document === 'undefined') {
242
+ return null;
243
+ }
244
+ if (overlayElement) {
245
+ const previous = overlayElement.style.pointerEvents;
246
+ overlayElement.style.pointerEvents = 'none';
247
+ const elements = document.elementsFromPoint(point.x, point.y);
248
+ overlayElement.style.pointerEvents = previous;
249
+ for (const element of elements) {
250
+ if (!(element instanceof HTMLElement)) {
251
+ continue;
252
+ }
253
+ if (overlayElement.contains(element)) {
254
+ continue;
255
+ }
256
+ if (element === document.body || element === document.documentElement) {
257
+ continue;
258
+ }
259
+ const rect = element.getBoundingClientRect();
260
+ if (rect.width <= 2 || rect.height <= 2) {
261
+ continue;
262
+ }
263
+ return element;
264
+ }
265
+ return null;
266
+ }
267
+ return document.elementFromPoint(point.x, point.y);
268
+ };
269
+
270
+ const createId = () => typeof crypto !== 'undefined' && 'randomUUID' in crypto
271
+ ? crypto.randomUUID()
272
+ : `${Date.now()}-${Math.random()}`;
273
+ const formatValue = (value) => Math.round(value);
274
+
275
+ class inspectorComponent {
276
+ constructor(platformId) {
277
+ this.Math = Math;
278
+ this.formatValue = formatValue;
279
+ this.guideHitboxSize = GUIDE_HITBOX_SIZE;
280
+ this.overlayRoot = viewChild("overlayRoot");
281
+ this.highlightColor = "#4f8cff";
282
+ this.guideColor = "#ff7a00";
283
+ this.hoverHighlightEnabled = true;
284
+ this.persistOnReload = false;
285
+ this.enabled = signal(false);
286
+ this.toolMode = signal("none");
287
+ this.guideOrientation = signal("vertical");
288
+ this.guides = signal([]);
289
+ this.hoverRect = signal(null);
290
+ this.selectedGuideId = signal(null);
291
+ this.selectedMeasurement = signal(null);
292
+ this.showTypography = signal(true);
293
+ this.altPressed = signal(false);
294
+ this.distanceOverlay = signal(null);
295
+ this.textBlocks = signal([]);
296
+ this.guideMenuOpen = signal(false);
297
+ this.selectedMetaLine = computed(() => {
298
+ const selected = this.selectedMeasurement();
299
+ if (!selected) {
300
+ return "";
301
+ }
302
+ return `${selected.label} · ${selected.styles.fontSize} / ${selected.styles.lineHeight} / ${selected.styles.color}`;
303
+ });
304
+ this.hydrated = signal(false);
305
+ this.history = signal([]);
306
+ this.historyIndex = signal(-1);
307
+ this.draggingGuideId = null;
308
+ this.selectedElement = null;
309
+ this.hoverElement = null;
310
+ this.canUndo = computed(() => this.historyIndex() > 0);
311
+ this.canRedo = computed(() => this.historyIndex() >= 0 &&
312
+ this.historyIndex() < this.history().length - 1);
313
+ this.trackGuide = (_index, guide) => guide.id;
314
+ this.trackTextBlock = (_index, block) => block.id;
315
+ this.trackConnector = (_index, connector) => `${connector.x1}:${connector.y1}:${connector.x2}:${connector.y2}`;
316
+ this.isBrowser = isPlatformBrowser(platformId);
317
+ effect(() => {
318
+ if (!this.persistOnReload || !this.isBrowser || !this.hydrated()) {
319
+ return;
320
+ }
321
+ const payload = {
322
+ version: inspector_STATE_VERSION,
323
+ enabled: this.enabled(),
324
+ toolMode: this.toolMode(),
325
+ guideOrientation: this.guideOrientation(),
326
+ guides: this.guides(),
327
+ showTypography: this.showTypography(),
328
+ };
329
+ window.localStorage.setItem(inspector_STORAGE_KEY, JSON.stringify(payload));
330
+ });
331
+ }
332
+ ngOnInit() {
333
+ if (!this.isBrowser || !this.persistOnReload) {
334
+ this.hydrated.set(true);
335
+ return;
336
+ }
337
+ const stored = window.localStorage.getItem(inspector_STORAGE_KEY);
338
+ if (!stored) {
339
+ this.hydrated.set(true);
340
+ return;
341
+ }
342
+ try {
343
+ const parsed = JSON.parse(stored);
344
+ if (parsed.version === inspector_STATE_VERSION) {
345
+ this.enabled.set(parsed.enabled);
346
+ this.toolMode.set(parsed.toolMode);
347
+ this.guideOrientation.set(parsed.guideOrientation);
348
+ this.guides.set(parsed.guides ?? []);
349
+ this.showTypography.set(parsed.showTypography ?? true);
350
+ this.recordHistory(parsed.guides ?? []);
351
+ }
352
+ }
353
+ catch {
354
+ // ignore malformed state
355
+ }
356
+ this.hydrated.set(true);
357
+ this.refreshTypographyBlocks();
358
+ }
359
+ setToolMode(mode) {
360
+ if (!this.enabled()) {
361
+ this.enabled.set(true);
362
+ }
363
+ this.toolMode.set(this.toolMode() === mode ? "none" : mode);
364
+ this.hoverRect.set(null);
365
+ this.distanceOverlay.set(null);
366
+ this.refreshTypographyBlocks();
367
+ }
368
+ setGuideOrientation(orientation) {
369
+ this.guideOrientation.set(orientation);
370
+ }
371
+ toggleTypography() {
372
+ this.showTypography.update((value) => !value);
373
+ this.refreshTypographyBlocks();
374
+ }
375
+ toggleGuideMenu() {
376
+ this.guideMenuOpen.update((value) => !value);
377
+ }
378
+ clearGuides() {
379
+ this.guides.set([]);
380
+ this.selectedGuideId.set(null);
381
+ this.recordHistory([]);
382
+ }
383
+ startGuideDrag(event, guide) {
384
+ event.preventDefault();
385
+ event.stopPropagation();
386
+ this.draggingGuideId = guide.id;
387
+ this.selectedGuideId.set(guide.id);
388
+ }
389
+ formatEdges(edges) {
390
+ return `${formatValue(edges.top)} ${formatValue(edges.right)} ${formatValue(edges.bottom)} ${formatValue(edges.left)}`;
391
+ }
392
+ formatGap(selected) {
393
+ return `${formatValue(selected.gap.row)} / ${formatValue(selected.gap.column)}`;
394
+ }
395
+ handleKeydown(event) {
396
+ const key = event.key.toLowerCase();
397
+ if (key === "m") {
398
+ event.preventDefault();
399
+ this.enabled.update((value) => !value);
400
+ if (!this.enabled()) {
401
+ this.toolMode.set("none");
402
+ this.hoverRect.set(null);
403
+ this.selectedGuideId.set(null);
404
+ this.distanceOverlay.set(null);
405
+ this.textBlocks.set([]);
406
+ }
407
+ this.refreshTypographyBlocks();
408
+ return;
409
+ }
410
+ if (!this.enabled()) {
411
+ return;
412
+ }
413
+ if ((event.metaKey || event.ctrlKey) && key === "z") {
414
+ event.preventDefault();
415
+ event.shiftKey ? this.redo() : this.undo();
416
+ return;
417
+ }
418
+ if (key === "s") {
419
+ event.preventDefault();
420
+ this.setToolMode("select");
421
+ return;
422
+ }
423
+ if (key === "g") {
424
+ event.preventDefault();
425
+ this.setToolMode("guides");
426
+ return;
427
+ }
428
+ if (key === "h") {
429
+ event.preventDefault();
430
+ this.setGuideOrientation("horizontal");
431
+ return;
432
+ }
433
+ if (key === "v") {
434
+ event.preventDefault();
435
+ this.setGuideOrientation("vertical");
436
+ return;
437
+ }
438
+ if (event.key === "Alt") {
439
+ this.altPressed.set(true);
440
+ this.updateDistanceOverlay();
441
+ return;
442
+ }
443
+ if (key === "escape") {
444
+ event.preventDefault();
445
+ this.selectedMeasurement.set(null);
446
+ this.selectedGuideId.set(null);
447
+ this.hoverRect.set(null);
448
+ this.distanceOverlay.set(null);
449
+ this.selectedElement = null;
450
+ this.hoverElement = null;
451
+ this.clearGuides();
452
+ this.refreshTypographyBlocks();
453
+ return;
454
+ }
455
+ if ((key === "backspace" || key === "delete") && this.selectedGuideId()) {
456
+ event.preventDefault();
457
+ const nextGuides = this.guides().filter((guide) => guide.id !== this.selectedGuideId());
458
+ this.guides.set(nextGuides);
459
+ this.selectedGuideId.set(null);
460
+ this.recordHistory(nextGuides);
461
+ }
462
+ }
463
+ handleKeyup(event) {
464
+ if (event.key === "Alt") {
465
+ this.altPressed.set(false);
466
+ this.distanceOverlay.set(null);
467
+ }
468
+ }
469
+ handlePointerMove(event) {
470
+ if (!this.enabled() || !this.isBrowser) {
471
+ return;
472
+ }
473
+ if (this.draggingGuideId) {
474
+ const viewport = getViewportSize();
475
+ this.guides.set(this.guides().map((guide) => {
476
+ if (guide.id !== this.draggingGuideId) {
477
+ return guide;
478
+ }
479
+ const rawPosition = guide.orientation === "vertical" ? event.clientX : event.clientY;
480
+ const max = guide.orientation === "vertical" ? viewport.width : viewport.height;
481
+ return { ...guide, position: clamp(rawPosition, 0, max) };
482
+ }));
483
+ return;
484
+ }
485
+ if (this.toolMode() !== "select" || !this.hoverHighlightEnabled) {
486
+ this.hoverRect.set(null);
487
+ this.hoverElement = null;
488
+ if (!this.altPressed()) {
489
+ return;
490
+ }
491
+ }
492
+ const target = getTargetElement({ x: event.clientX, y: event.clientY }, this.overlayRoot()?.nativeElement ?? null);
493
+ if (!target) {
494
+ this.hoverRect.set(null);
495
+ this.hoverElement = null;
496
+ this.distanceOverlay.set(null);
497
+ return;
498
+ }
499
+ const rect = getRectFromDom(target);
500
+ this.hoverElement = target;
501
+ if (!rectsEqual(rect, this.hoverRect())) {
502
+ this.hoverRect.set(rect);
503
+ }
504
+ this.updateDistanceOverlay();
505
+ this.refreshTypographyBlocks();
506
+ }
507
+ handlePointerUp() {
508
+ if (!this.draggingGuideId) {
509
+ return;
510
+ }
511
+ this.recordHistory(this.guides());
512
+ this.draggingGuideId = null;
513
+ }
514
+ handleClick(event) {
515
+ if (!this.enabled() || !this.isBrowser) {
516
+ return;
517
+ }
518
+ const overlay = this.overlayRoot()?.nativeElement ?? null;
519
+ if (this.toolMode() === "guides") {
520
+ if (overlay && overlay.contains(event.target)) {
521
+ return;
522
+ }
523
+ this.addGuide({
524
+ id: createId(),
525
+ orientation: this.guideOrientation(),
526
+ position: this.guideOrientation() === "vertical"
527
+ ? this.snapGuide(event.clientX)
528
+ : this.snapGuide(event.clientY),
529
+ });
530
+ return;
531
+ }
532
+ if (this.toolMode() !== "select") {
533
+ return;
534
+ }
535
+ const target = getTargetElement({ x: event.clientX, y: event.clientY }, overlay);
536
+ if (!target) {
537
+ this.selectedMeasurement.set(null);
538
+ this.selectedElement = null;
539
+ this.distanceOverlay.set(null);
540
+ this.refreshTypographyBlocks();
541
+ return;
542
+ }
543
+ this.selectedMeasurement.set(getInspectMeasurement(target));
544
+ this.selectedElement = target;
545
+ this.selectedGuideId.set(this.findGuideAtPoint(event.clientX, event.clientY));
546
+ this.updateDistanceOverlay();
547
+ this.refreshTypographyBlocks();
548
+ }
549
+ handleViewportChange() {
550
+ if (this.selectedElement && document.contains(this.selectedElement)) {
551
+ this.selectedMeasurement.set(getInspectMeasurement(this.selectedElement));
552
+ }
553
+ this.updateDistanceOverlay();
554
+ this.refreshTypographyBlocks();
555
+ }
556
+ updateDistanceOverlay() {
557
+ if (!this.altPressed() ||
558
+ !this.selectedElement ||
559
+ !this.hoverElement ||
560
+ this.selectedElement === this.hoverElement ||
561
+ this.selectedElement.contains(this.hoverElement) ||
562
+ this.hoverElement.contains(this.selectedElement)) {
563
+ this.distanceOverlay.set(null);
564
+ return;
565
+ }
566
+ this.distanceOverlay.set(getDistanceOverlay(getRectFromDom(this.selectedElement), getRectFromDom(this.hoverElement)));
567
+ }
568
+ refreshTypographyBlocks() {
569
+ if (!this.isBrowser || !this.enabled() || !this.showTypography()) {
570
+ this.textBlocks.set([]);
571
+ return;
572
+ }
573
+ const viewport = getViewportSize();
574
+ this.textBlocks.set(getVisibleTextBlocks(this.overlayRoot()?.nativeElement ?? null).map((block) => ({
575
+ ...block,
576
+ rect: {
577
+ ...block.rect,
578
+ left: clamp(block.rect.left, 0, viewport.width - 12),
579
+ top: clamp(block.rect.top, 14, viewport.height),
580
+ },
581
+ })));
582
+ }
583
+ addGuide(guide) {
584
+ const nextGuides = [...this.guides(), guide];
585
+ this.guides.set(nextGuides);
586
+ this.selectedGuideId.set(guide.id);
587
+ this.recordHistory(nextGuides);
588
+ }
589
+ snapGuide(position) {
590
+ for (const guide of this.guides()) {
591
+ if (Math.abs(guide.position - position) <= GUIDE_SNAP_DISTANCE) {
592
+ return guide.position;
593
+ }
594
+ }
595
+ return position;
596
+ }
597
+ findGuideAtPoint(x, y) {
598
+ for (const guide of this.guides()) {
599
+ if (guide.orientation === "vertical" &&
600
+ Math.abs(guide.position - x) <= GUIDE_HITBOX_SIZE) {
601
+ return guide.id;
602
+ }
603
+ if (guide.orientation === "horizontal" &&
604
+ Math.abs(guide.position - y) <= GUIDE_HITBOX_SIZE) {
605
+ return guide.id;
606
+ }
607
+ }
608
+ return null;
609
+ }
610
+ undo() {
611
+ if (!this.canUndo()) {
612
+ return;
613
+ }
614
+ const nextIndex = this.historyIndex() - 1;
615
+ this.historyIndex.set(nextIndex);
616
+ this.guides.set(this.cloneGuides(this.history()[nextIndex]));
617
+ }
618
+ redo() {
619
+ if (!this.canRedo()) {
620
+ return;
621
+ }
622
+ const nextIndex = this.historyIndex() + 1;
623
+ this.historyIndex.set(nextIndex);
624
+ this.guides.set(this.cloneGuides(this.history()[nextIndex]));
625
+ }
626
+ recordHistory(guides) {
627
+ const base = this.history().slice(0, this.historyIndex() + 1);
628
+ base.push(this.cloneGuides(guides));
629
+ this.history.set(base);
630
+ this.historyIndex.set(base.length - 1);
631
+ }
632
+ cloneGuides(guides) {
633
+ return guides.map((guide) => ({ ...guide }));
634
+ }
635
+ 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 }); }
636
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: inspectorComponent, isStandalone: true, selector: "inspector-overlay", inputs: { highlightColor: "highlightColor", guideColor: "guideColor", hoverHighlightEnabled: "hoverHighlightEnabled", persistOnReload: "persistOnReload" }, host: { listeners: { "window:keydown": "handleKeydown($event)", "window:keyup": "handleKeyup($event)", "window:pointermove": "handlePointerMove($event)", "window:pointerup": "handlePointerUp()", "window:click": "handleClick($event)", "window:resize": "handleViewportChange()", "window:scroll": "handleViewportChange()" } }, viewQueries: [{ propertyName: "overlayRoot", first: true, predicate: ["overlayRoot"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"inspector-root\" #overlayRoot>\n <div class=\"inspector-toolbar\">\n <div class=\"inspector-toolbar__rail\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'select'\"\n (click)=\"setToolMode('select')\"\n title=\"Select mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2l8 8-3 .7L6.7 13 6 10 3 2z\" />\n </svg>\n </button>\n\n <div class=\"inspector-guide-group\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'guides'\"\n (click)=\"setToolMode('guides')\"\n title=\"Guides mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4 2h1v12H4zM11 2h1v12h-1zM2 4h12v1H2zM2 11h12v1H2z\" />\n </svg>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button inspector-icon-button--ghost\"\n [class.is-active]=\"guideMenuOpen()\"\n (click)=\"toggleGuideMenu()\"\n title=\"Guide options\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4.47 6.97L8 10.5l3.53-3.53-.94-.94L8 8.62 5.41 6.03z\" />\n </svg>\n </button>\n\n @if (guideMenuOpen()) {\n <div class=\"inspector-guide-menu\">\n <button\n type=\"button\"\n class=\"inspector-segment\"\n [class.is-active]=\"guideOrientation() === 'vertical'\"\n (click)=\"setGuideOrientation('vertical')\"\n >\n Vertical\n </button>\n <button\n type=\"button\"\n class=\"inspector-segment\"\n [class.is-active]=\"guideOrientation() === 'horizontal'\"\n (click)=\"setGuideOrientation('horizontal')\"\n >\n Horizontal\n </button>\n <button\n type=\"button\"\n class=\"inspector-segment inspector-segment--danger\"\n (click)=\"clearGuides()\"\n >\n Clear\n </button>\n </div>\n }\n </div>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"showTypography()\"\n (click)=\"toggleTypography()\"\n title=\"Typography mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 4V2h10v2h-4v10H7V4z\" />\n </svg>\n </button>\n </div>\n </div>\n\n @if (enabled() && toolMode() === \"select\") {\n @if (hoverRect() && hoverHighlightEnabled) {\n <div\n class=\"inspector-box inspector-box--hover\"\n [style.left.px]=\"hoverRect()!.left\"\n [style.top.px]=\"hoverRect()!.top\"\n [style.width.px]=\"hoverRect()!.width\"\n [style.height.px]=\"hoverRect()!.height\"\n [style.--inspector-accent]=\"highlightColor\"\n ></div>\n }\n\n @if (selectedMeasurement(); as selected) {\n @if (selected.gap.active && selected.parentRect) {\n <div\n class=\"inspector-box inspector-box--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top\"\n [style.width.px]=\"selected.parentRect.width\"\n [style.height.px]=\"selected.parentRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top - 8\"\n >\n G {{ formatGap(selected) }}\n </div>\n }\n\n <div\n class=\"inspector-box inspector-box--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top\"\n [style.width.px]=\"selected.marginRect.width\"\n [style.height.px]=\"selected.marginRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top - 8\"\n >\n M {{ formatEdges(selected.margin) }}\n </div>\n\n <div\n class=\"inspector-box inspector-box--selected\"\n [style.left.px]=\"selected.rect.left\"\n [style.top.px]=\"selected.rect.top\"\n [style.width.px]=\"selected.rect.width\"\n [style.height.px]=\"selected.rect.height\"\n [style.--inspector-accent]=\"highlightColor\"\n >\n <div class=\"inspector-label\">{{ selectedMetaLine() }}</div>\n </div>\n\n <div\n class=\"inspector-box inspector-box--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"selected.paddingRect.top\"\n [style.width.px]=\"selected.paddingRect.width\"\n [style.height.px]=\"selected.paddingRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"\n selected.paddingRect.top + selected.paddingRect.height + 8\n \"\n >\n P {{ formatEdges(selected.padding) }}\n </div>\n }\n }\n\n @if (enabled() && showTypography()) {\n @for (block of textBlocks(); track block.id) {\n <div\n class=\"inspector-text-chip\"\n [style.left.px]=\"block.rect.left\"\n [style.top.px]=\"block.rect.top - 8\"\n >\n <span\n class=\"inspector-text-chip__swatch\"\n [style.background]=\"block.styles.color\"\n ></span>\n <span>{{ block.styles.fontSize }}</span>\n <span>{{ block.styles.lineHeight }}</span>\n <span>{{ block.styles.color }}</span>\n </div>\n }\n }\n\n @if (enabled() && altPressed() && distanceOverlay(); as distance) {\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectA.left\"\n [style.top.px]=\"distance.rectA.top\"\n [style.width.px]=\"distance.rectA.width\"\n [style.height.px]=\"distance.rectA.height\"\n ></div>\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectB.left\"\n [style.top.px]=\"distance.rectB.top\"\n [style.width.px]=\"distance.rectB.width\"\n [style.height.px]=\"distance.rectB.height\"\n ></div>\n\n @for (\n connector of distance.connectors;\n track trackConnector($index, connector)\n ) {\n <div\n class=\"inspector-connector\"\n [class.inspector-connector--vertical]=\"connector.x1 === connector.x2\"\n [class.inspector-connector--horizontal]=\"connector.y1 === connector.y2\"\n [style.left.px]=\"Math.min(connector.x1, connector.x2)\"\n [style.top.px]=\"Math.min(connector.y1, connector.y2)\"\n [style.width.px]=\"Math.max(1, Math.abs(connector.x2 - connector.x1))\"\n [style.height.px]=\"Math.max(1, Math.abs(connector.y2 - connector.y1))\"\n ></div>\n }\n\n @if (distance.horizontal && distance.horizontal.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--horizontal\"\n [style.left.px]=\"\n Math.min(distance.horizontal.x1!, distance.horizontal.x2!)\n \"\n [style.top.px]=\"distance.horizontal.y!\"\n [style.width.px]=\"\n Math.abs(distance.horizontal.x2! - distance.horizontal.x1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag\"\n [style.left.px]=\"\n (distance.horizontal.x1! + distance.horizontal.x2!) / 2\n \"\n [style.top.px]=\"distance.horizontal.y! + 10\"\n >\n {{ formatValue(distance.horizontal.value) }}\n </div>\n }\n\n @if (distance.vertical && distance.vertical.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--vertical\"\n [style.left.px]=\"distance.vertical.x!\"\n [style.top.px]=\"Math.min(distance.vertical.y1!, distance.vertical.y2!)\"\n [style.height.px]=\"\n Math.abs(distance.vertical.y2! - distance.vertical.y1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag inspector-distance-tag--vertical\"\n [style.left.px]=\"distance.vertical.x! + 10\"\n [style.top.px]=\"(distance.vertical.y1! + distance.vertical.y2!) / 2\"\n >\n {{ formatValue(distance.vertical.value) }}\n </div>\n }\n }\n\n @if (enabled()) {\n @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:2147483647;pointer-events:none;font-family:SF Pro Display,Segoe UI,sans-serif}.inspector-root{position:fixed;inset:0;pointer-events:none}.inspector-toolbar{position:fixed;top:18px;right:18px;display:block;pointer-events:auto}.inspector-toolbar__rail,.inspector-guide-menu{display:flex;gap:6px;padding:6px;border-radius:14px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.inspector-toolbar__rail{align-items:center}.inspector-guide-group{position:relative;display:flex;gap:4px;align-items:center}.inspector-guide-menu{position:absolute;top:calc(100% + 8px);right:0;min-width:max-content;flex-direction:column;align-items:stretch}.inspector-guide-menu .inspector-segment{justify-content:flex-start;width:100%}.inspector-icon-button,.inspector-segment{display:inline-flex;align-items:center;justify-content:center;height:28px;border:0;border-radius:10px;background:transparent;color:var(--inspector-muted);cursor:pointer;transition:background .12s ease,color .12s ease;font:inherit;font-size:11px;letter-spacing:.02em}.inspector-icon-button{width:28px}.inspector-icon-button svg{width:14px;height:14px;fill:currentColor}.inspector-icon-button:hover,.inspector-segment:hover{background:#ffffff14;color:var(--inspector-text)}.inspector-icon-button.is-active,.inspector-segment.is-active{background:#4f8cff2e;color:#dbeafe}.inspector-icon-button--ghost.is-active{background:#ffffff14;color:var(--inspector-text)}.inspector-segment{padding:0 10px;white-space:nowrap}.inspector-segment--danger:hover{background:#ef444429;color:#fecaca}.inspector-box{position:fixed;box-sizing:border-box;pointer-events:none;border:1px solid var(--inspector-accent, var(--inspector-blue));background:color-mix(in srgb,var(--inspector-accent, var(--inspector-blue)) 8%,transparent)}.inspector-box--hover{opacity:.55}.inspector-box--selected{border-width:1.5px}.inspector-box--margin{border-color:#f59e0bd1;background:#f59e0b14}.inspector-box--padding{border-color:#22c55ed1;background:#22c55e14}.inspector-box--gap{border-color:#a855f7b8;background:#a855f70a}.inspector-box--distance{border-style:dashed;border-color:#4f8cffb3;background:transparent}.inspector-label,.inspector-box-tag,.inspector-text-chip,.inspector-distance-tag{font-variant-numeric:tabular-nums}.inspector-label{position:absolute;left:0;top:0;transform:translateY(calc(-100% - 8px));max-width:min(420px,calc(100vw - 24px));padding:6px 9px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);color:var(--inspector-text);box-shadow:0 18px 40px #00000042;font-size:11px;line-height:1;white-space:nowrap}.inspector-box-tag{position:fixed;transform:translateY(-100%);padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:var(--inspector-text);box-shadow:0 12px 24px #0003;font-size:10px;line-height:1;pointer-events:none}.inspector-box-tag--margin{color:#fbbf24}.inspector-box-tag--padding{color:#86efac;transform:translateY(0)}.inspector-box-tag--gap{color:#d8b4fe}.inspector-text-chip{position:fixed;transform:translateY(-100%);display:inline-flex;gap:6px;align-items:center;padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:#e5e7eb;box-shadow:0 12px 24px #00000038;font-size:10px;line-height:1;white-space:nowrap;pointer-events:none}.inspector-text-chip__swatch{width:8px;height:8px;border-radius:999px;border:1px solid rgba(255,255,255,.18);flex:0 0 auto}.inspector-connector{position:fixed;border-color:#4f8cffb8;border-style:dashed;pointer-events:none}.inspector-connector--vertical{border-left-width:1px;width:0}.inspector-connector--horizontal{border-top-width:1px;height:0}.inspector-distance-line{position:fixed;background:var(--inspector-blue);pointer-events:none}.inspector-distance-line--horizontal{height:1px}.inspector-distance-line--vertical{width:1px}.inspector-distance-tag{position:fixed;transform:translate(-50%);padding:5px 7px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f2;color:var(--inspector-text);font-size:10px;line-height:1;pointer-events:none}.inspector-distance-tag--vertical{transform:translateY(-50%)}.inspector-guide{position:fixed;margin:0;padding:0;border:0;background:transparent;pointer-events:auto;cursor:grab}.inspector-guide--vertical{top:0;width:14px;height:100vh;transform:translate(-50%)}.inspector-guide--horizontal{left:0;width:100vw;height:14px;transform:translateY(-50%)}.inspector-guide-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 }); }
637
+ }
638
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: inspectorComponent, decorators: [{
639
+ type: Component,
640
+ args: [{ selector: "inspector-overlay", standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"inspector-root\" #overlayRoot>\n <div class=\"inspector-toolbar\">\n <div class=\"inspector-toolbar__rail\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'select'\"\n (click)=\"setToolMode('select')\"\n title=\"Select mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 2l8 8-3 .7L6.7 13 6 10 3 2z\" />\n </svg>\n </button>\n\n <div class=\"inspector-guide-group\">\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"toolMode() === 'guides'\"\n (click)=\"setToolMode('guides')\"\n title=\"Guides mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4 2h1v12H4zM11 2h1v12h-1zM2 4h12v1H2zM2 11h12v1H2z\" />\n </svg>\n </button>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button inspector-icon-button--ghost\"\n [class.is-active]=\"guideMenuOpen()\"\n (click)=\"toggleGuideMenu()\"\n title=\"Guide options\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M4.47 6.97L8 10.5l3.53-3.53-.94-.94L8 8.62 5.41 6.03z\" />\n </svg>\n </button>\n\n @if (guideMenuOpen()) {\n <div class=\"inspector-guide-menu\">\n <button\n type=\"button\"\n class=\"inspector-segment\"\n [class.is-active]=\"guideOrientation() === 'vertical'\"\n (click)=\"setGuideOrientation('vertical')\"\n >\n Vertical\n </button>\n <button\n type=\"button\"\n class=\"inspector-segment\"\n [class.is-active]=\"guideOrientation() === 'horizontal'\"\n (click)=\"setGuideOrientation('horizontal')\"\n >\n Horizontal\n </button>\n <button\n type=\"button\"\n class=\"inspector-segment inspector-segment--danger\"\n (click)=\"clearGuides()\"\n >\n Clear\n </button>\n </div>\n }\n </div>\n\n <button\n type=\"button\"\n class=\"inspector-icon-button\"\n [class.is-active]=\"showTypography()\"\n (click)=\"toggleTypography()\"\n title=\"Typography mode\"\n >\n <svg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\n <path d=\"M3 4V2h10v2h-4v10H7V4z\" />\n </svg>\n </button>\n </div>\n </div>\n\n @if (enabled() && toolMode() === \"select\") {\n @if (hoverRect() && hoverHighlightEnabled) {\n <div\n class=\"inspector-box inspector-box--hover\"\n [style.left.px]=\"hoverRect()!.left\"\n [style.top.px]=\"hoverRect()!.top\"\n [style.width.px]=\"hoverRect()!.width\"\n [style.height.px]=\"hoverRect()!.height\"\n [style.--inspector-accent]=\"highlightColor\"\n ></div>\n }\n\n @if (selectedMeasurement(); as selected) {\n @if (selected.gap.active && selected.parentRect) {\n <div\n class=\"inspector-box inspector-box--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top\"\n [style.width.px]=\"selected.parentRect.width\"\n [style.height.px]=\"selected.parentRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--gap\"\n [style.left.px]=\"selected.parentRect.left\"\n [style.top.px]=\"selected.parentRect.top - 8\"\n >\n G {{ formatGap(selected) }}\n </div>\n }\n\n <div\n class=\"inspector-box inspector-box--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top\"\n [style.width.px]=\"selected.marginRect.width\"\n [style.height.px]=\"selected.marginRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--margin\"\n [style.left.px]=\"selected.marginRect.left\"\n [style.top.px]=\"selected.marginRect.top - 8\"\n >\n M {{ formatEdges(selected.margin) }}\n </div>\n\n <div\n class=\"inspector-box inspector-box--selected\"\n [style.left.px]=\"selected.rect.left\"\n [style.top.px]=\"selected.rect.top\"\n [style.width.px]=\"selected.rect.width\"\n [style.height.px]=\"selected.rect.height\"\n [style.--inspector-accent]=\"highlightColor\"\n >\n <div class=\"inspector-label\">{{ selectedMetaLine() }}</div>\n </div>\n\n <div\n class=\"inspector-box inspector-box--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"selected.paddingRect.top\"\n [style.width.px]=\"selected.paddingRect.width\"\n [style.height.px]=\"selected.paddingRect.height\"\n ></div>\n <div\n class=\"inspector-box-tag inspector-box-tag--padding\"\n [style.left.px]=\"selected.paddingRect.left\"\n [style.top.px]=\"\n selected.paddingRect.top + selected.paddingRect.height + 8\n \"\n >\n P {{ formatEdges(selected.padding) }}\n </div>\n }\n }\n\n @if (enabled() && showTypography()) {\n @for (block of textBlocks(); track block.id) {\n <div\n class=\"inspector-text-chip\"\n [style.left.px]=\"block.rect.left\"\n [style.top.px]=\"block.rect.top - 8\"\n >\n <span\n class=\"inspector-text-chip__swatch\"\n [style.background]=\"block.styles.color\"\n ></span>\n <span>{{ block.styles.fontSize }}</span>\n <span>{{ block.styles.lineHeight }}</span>\n <span>{{ block.styles.color }}</span>\n </div>\n }\n }\n\n @if (enabled() && altPressed() && distanceOverlay(); as distance) {\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectA.left\"\n [style.top.px]=\"distance.rectA.top\"\n [style.width.px]=\"distance.rectA.width\"\n [style.height.px]=\"distance.rectA.height\"\n ></div>\n <div\n class=\"inspector-box inspector-box--distance\"\n [style.left.px]=\"distance.rectB.left\"\n [style.top.px]=\"distance.rectB.top\"\n [style.width.px]=\"distance.rectB.width\"\n [style.height.px]=\"distance.rectB.height\"\n ></div>\n\n @for (\n connector of distance.connectors;\n track trackConnector($index, connector)\n ) {\n <div\n class=\"inspector-connector\"\n [class.inspector-connector--vertical]=\"connector.x1 === connector.x2\"\n [class.inspector-connector--horizontal]=\"connector.y1 === connector.y2\"\n [style.left.px]=\"Math.min(connector.x1, connector.x2)\"\n [style.top.px]=\"Math.min(connector.y1, connector.y2)\"\n [style.width.px]=\"Math.max(1, Math.abs(connector.x2 - connector.x1))\"\n [style.height.px]=\"Math.max(1, Math.abs(connector.y2 - connector.y1))\"\n ></div>\n }\n\n @if (distance.horizontal && distance.horizontal.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--horizontal\"\n [style.left.px]=\"\n Math.min(distance.horizontal.x1!, distance.horizontal.x2!)\n \"\n [style.top.px]=\"distance.horizontal.y!\"\n [style.width.px]=\"\n Math.abs(distance.horizontal.x2! - distance.horizontal.x1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag\"\n [style.left.px]=\"\n (distance.horizontal.x1! + distance.horizontal.x2!) / 2\n \"\n [style.top.px]=\"distance.horizontal.y! + 10\"\n >\n {{ formatValue(distance.horizontal.value) }}\n </div>\n }\n\n @if (distance.vertical && distance.vertical.value > 0) {\n <div\n class=\"inspector-distance-line inspector-distance-line--vertical\"\n [style.left.px]=\"distance.vertical.x!\"\n [style.top.px]=\"Math.min(distance.vertical.y1!, distance.vertical.y2!)\"\n [style.height.px]=\"\n Math.abs(distance.vertical.y2! - distance.vertical.y1!)\n \"\n ></div>\n <div\n class=\"inspector-distance-tag inspector-distance-tag--vertical\"\n [style.left.px]=\"distance.vertical.x! + 10\"\n [style.top.px]=\"(distance.vertical.y1! + distance.vertical.y2!) / 2\"\n >\n {{ formatValue(distance.vertical.value) }}\n </div>\n }\n }\n\n @if (enabled()) {\n @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:2147483647;pointer-events:none;font-family:SF Pro Display,Segoe UI,sans-serif}.inspector-root{position:fixed;inset:0;pointer-events:none}.inspector-toolbar{position:fixed;top:18px;right:18px;display:block;pointer-events:auto}.inspector-toolbar__rail,.inspector-guide-menu{display:flex;gap:6px;padding:6px;border-radius:14px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);box-shadow:0 18px 40px #00000052;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.inspector-toolbar__rail{align-items:center}.inspector-guide-group{position:relative;display:flex;gap:4px;align-items:center}.inspector-guide-menu{position:absolute;top:calc(100% + 8px);right:0;min-width:max-content;flex-direction:column;align-items:stretch}.inspector-guide-menu .inspector-segment{justify-content:flex-start;width:100%}.inspector-icon-button,.inspector-segment{display:inline-flex;align-items:center;justify-content:center;height:28px;border:0;border-radius:10px;background:transparent;color:var(--inspector-muted);cursor:pointer;transition:background .12s ease,color .12s ease;font:inherit;font-size:11px;letter-spacing:.02em}.inspector-icon-button{width:28px}.inspector-icon-button svg{width:14px;height:14px;fill:currentColor}.inspector-icon-button:hover,.inspector-segment:hover{background:#ffffff14;color:var(--inspector-text)}.inspector-icon-button.is-active,.inspector-segment.is-active{background:#4f8cff2e;color:#dbeafe}.inspector-icon-button--ghost.is-active{background:#ffffff14;color:var(--inspector-text)}.inspector-segment{padding:0 10px;white-space:nowrap}.inspector-segment--danger:hover{background:#ef444429;color:#fecaca}.inspector-box{position:fixed;box-sizing:border-box;pointer-events:none;border:1px solid var(--inspector-accent, var(--inspector-blue));background:color-mix(in srgb,var(--inspector-accent, var(--inspector-blue)) 8%,transparent)}.inspector-box--hover{opacity:.55}.inspector-box--selected{border-width:1.5px}.inspector-box--margin{border-color:#f59e0bd1;background:#f59e0b14}.inspector-box--padding{border-color:#22c55ed1;background:#22c55e14}.inspector-box--gap{border-color:#a855f7b8;background:#a855f70a}.inspector-box--distance{border-style:dashed;border-color:#4f8cffb3;background:transparent}.inspector-label,.inspector-box-tag,.inspector-text-chip,.inspector-distance-tag{font-variant-numeric:tabular-nums}.inspector-label{position:absolute;left:0;top:0;transform:translateY(calc(-100% - 8px));max-width:min(420px,calc(100vw - 24px));padding:6px 9px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:var(--inspector-panel);color:var(--inspector-text);box-shadow:0 18px 40px #00000042;font-size:11px;line-height:1;white-space:nowrap}.inspector-box-tag{position:fixed;transform:translateY(-100%);padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:var(--inspector-text);box-shadow:0 12px 24px #0003;font-size:10px;line-height:1;pointer-events:none}.inspector-box-tag--margin{color:#fbbf24}.inspector-box-tag--padding{color:#86efac;transform:translateY(0)}.inspector-box-tag--gap{color:#d8b4fe}.inspector-text-chip{position:fixed;transform:translateY(-100%);display:inline-flex;gap:6px;align-items:center;padding:4px 6px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f0;color:#e5e7eb;box-shadow:0 12px 24px #00000038;font-size:10px;line-height:1;white-space:nowrap;pointer-events:none}.inspector-text-chip__swatch{width:8px;height:8px;border-radius:999px;border:1px solid rgba(255,255,255,.18);flex:0 0 auto}.inspector-connector{position:fixed;border-color:#4f8cffb8;border-style:dashed;pointer-events:none}.inspector-connector--vertical{border-left-width:1px;width:0}.inspector-connector--horizontal{border-top-width:1px;height:0}.inspector-distance-line{position:fixed;background:var(--inspector-blue);pointer-events:none}.inspector-distance-line--horizontal{height:1px}.inspector-distance-line--vertical{width:1px}.inspector-distance-tag{position:fixed;transform:translate(-50%);padding:5px 7px;border-radius:999px;border:1px solid var(--inspector-panel-edge);background:#111827f2;color:var(--inspector-text);font-size:10px;line-height:1;pointer-events:none}.inspector-distance-tag--vertical{transform:translateY(-50%)}.inspector-guide{position:fixed;margin:0;padding:0;border:0;background:transparent;pointer-events:auto;cursor:grab}.inspector-guide--vertical{top:0;width:14px;height:100vh;transform:translate(-50%)}.inspector-guide--horizontal{left:0;width:100vw;height:14px;transform:translateY(-50%)}.inspector-guide-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"] }]
641
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
642
+ type: Inject,
643
+ args: [PLATFORM_ID]
644
+ }] }], propDecorators: { highlightColor: [{
645
+ type: Input
646
+ }], guideColor: [{
647
+ type: Input
648
+ }], hoverHighlightEnabled: [{
649
+ type: Input
650
+ }], persistOnReload: [{
651
+ type: Input
652
+ }], handleKeydown: [{
653
+ type: HostListener,
654
+ args: ["window:keydown", ["$event"]]
655
+ }], handleKeyup: [{
656
+ type: HostListener,
657
+ args: ["window:keyup", ["$event"]]
658
+ }], handlePointerMove: [{
659
+ type: HostListener,
660
+ args: ["window:pointermove", ["$event"]]
661
+ }], handlePointerUp: [{
662
+ type: HostListener,
663
+ args: ["window:pointerup"]
664
+ }], handleClick: [{
665
+ type: HostListener,
666
+ args: ["window:click", ["$event"]]
667
+ }], handleViewportChange: [{
668
+ type: HostListener,
669
+ args: ["window:resize"]
670
+ }, {
671
+ type: HostListener,
672
+ args: ["window:scroll"]
673
+ }] } });
674
+
675
+ /**
676
+ * Generated bundle index. Do not edit.
677
+ */
678
+
679
+ export { GUIDE_HITBOX_SIZE, GUIDE_SNAP_DISTANCE, clamp, createId, formatValue, getDistanceOverlay, getElementLabel, getInspectMeasurement, getRectFromDom, getTargetElement, getTextInspection, getViewportSize, getVisibleTextBlocks, inspectorComponent, inspector_STATE_VERSION, inspector_STORAGE_KEY, rectContainsPoint, rectsEqual };
680
+ //# sourceMappingURL=inspector-ng.mjs.map