@ng-prism/core 22.3.0 → 22.4.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.
Files changed (166) hide show
  1. package/README.md +13 -13
  2. package/dist/app/canvas/canvas-bg.styles.d.ts +7 -12
  3. package/dist/app/canvas/canvas-bg.styles.d.ts.map +1 -1
  4. package/dist/app/canvas/canvas-bg.styles.js +35 -37
  5. package/dist/app/canvas/measure-geometry.d.ts +140 -0
  6. package/dist/app/canvas/measure-geometry.d.ts.map +1 -0
  7. package/dist/app/canvas/measure-geometry.js +217 -0
  8. package/dist/app/canvas/measure-keyboard.d.ts +20 -0
  9. package/dist/app/canvas/measure-keyboard.d.ts.map +1 -0
  10. package/dist/app/canvas/measure-keyboard.js +34 -0
  11. package/dist/app/canvas/measure-quad.d.ts +114 -0
  12. package/dist/app/canvas/measure-quad.d.ts.map +1 -0
  13. package/dist/app/canvas/measure-quad.js +173 -0
  14. package/dist/app/canvas/measure-snap.d.ts +35 -0
  15. package/dist/app/canvas/measure-snap.d.ts.map +1 -0
  16. package/dist/app/canvas/measure-snap.js +63 -0
  17. package/dist/app/canvas/prism-canvas-measure.component.d.ts +142 -0
  18. package/dist/app/canvas/prism-canvas-measure.component.d.ts.map +1 -0
  19. package/dist/app/canvas/prism-canvas-measure.component.js +554 -0
  20. package/dist/app/canvas/prism-canvas-pin-row.component.d.ts +23 -0
  21. package/dist/app/canvas/prism-canvas-pin-row.component.d.ts.map +1 -0
  22. package/dist/app/canvas/prism-canvas-pin-row.component.js +64 -0
  23. package/dist/app/canvas/prism-canvas-toolbar.component.d.ts +22 -39
  24. package/dist/app/canvas/prism-canvas-toolbar.component.d.ts.map +1 -1
  25. package/dist/app/canvas/prism-canvas-toolbar.component.js +61 -69
  26. package/dist/app/canvas/prism-template-popover.component.js +1 -1
  27. package/dist/app/canvas/viewport-snap.d.ts +1 -1
  28. package/dist/app/canvas/viewport-snap.js +1 -1
  29. package/dist/app/component-head/head-metrics.d.ts +2 -2
  30. package/dist/app/component-head/head-metrics.js +1 -1
  31. package/dist/app/component-head/prism-component-head.component.d.ts +2 -2
  32. package/dist/app/component-head/prism-component-head.component.d.ts.map +1 -1
  33. package/dist/app/component-head/prism-component-head.component.js +11 -18
  34. package/dist/app/component-head/prism-head-gauge.component.js +1 -1
  35. package/dist/app/component-head/prism-head-info.component.d.ts +1 -1
  36. package/dist/app/component-head/prism-head-info.component.js +2 -2
  37. package/dist/app/component-head/prism-stat.component.d.ts +1 -1
  38. package/dist/app/component-head/prism-stat.component.js +2 -2
  39. package/dist/app/directives/prism-resizer.directive.d.ts +1 -1
  40. package/dist/app/directives/prism-resizer.directive.js +2 -2
  41. package/dist/app/directives/resize-value.d.ts +2 -2
  42. package/dist/app/directives/resize-value.js +2 -2
  43. package/dist/app/discovery-manifest.d.ts +2 -2
  44. package/dist/app/discovery-manifest.d.ts.map +1 -1
  45. package/dist/app/discovery-manifest.js +11 -15
  46. package/dist/app/header/prism-header.component.js +1 -1
  47. package/dist/app/icons/icon-registry.d.ts +2 -2
  48. package/dist/app/icons/icon-registry.d.ts.map +1 -1
  49. package/dist/app/icons/icon-registry.js +3 -2
  50. package/dist/app/icons/prism-icon.component.d.ts +2 -2
  51. package/dist/app/icons/prism-icon.component.js +4 -4
  52. package/dist/app/index.d.ts.map +1 -1
  53. package/dist/app/index.js +14 -21
  54. package/dist/app/overview/overview-visibility.d.ts +1 -1
  55. package/dist/app/overview/overview-visibility.js +1 -1
  56. package/dist/app/overview/prism-overview-cell.component.d.ts +2 -2
  57. package/dist/app/overview/prism-overview-cell.component.d.ts.map +1 -1
  58. package/dist/app/overview/prism-overview-cell.component.js +9 -10
  59. package/dist/app/overview/prism-overview.component.d.ts +2 -2
  60. package/dist/app/overview/prism-overview.component.js +2 -2
  61. package/dist/app/page-renderer/prism-page-renderer.component.js +1 -1
  62. package/dist/app/panels/a11y/a11y-audit.service.js +2 -2
  63. package/dist/app/panels/a11y/a11y-summary.d.ts +3 -4
  64. package/dist/app/panels/a11y/a11y-summary.d.ts.map +1 -1
  65. package/dist/app/panels/a11y/a11y-summary.js +3 -4
  66. package/dist/app/panels/a11y/a11y-thresholds.d.ts +1 -1
  67. package/dist/app/panels/a11y/a11y-thresholds.d.ts.map +1 -1
  68. package/dist/app/panels/a11y/a11y-thresholds.js +1 -1
  69. package/dist/app/panels/a11y/a11y.types.d.ts +1 -1
  70. package/dist/app/panels/a11y/a11y.types.d.ts.map +1 -1
  71. package/dist/app/panels/builtin-navigation-decorations.d.ts +1 -1
  72. package/dist/app/panels/builtin-navigation-decorations.js +1 -1
  73. package/dist/app/panels/controls/non-editable-input-types.js +1 -1
  74. package/dist/app/panels/panel-host/panel-badge.d.ts +1 -1
  75. package/dist/app/provide-prism.js +1 -1
  76. package/dist/app/renderer/known-inputs.d.ts +1 -1
  77. package/dist/app/renderer/known-inputs.js +1 -1
  78. package/dist/app/renderer/overlay-resolver.d.ts +17 -3
  79. package/dist/app/renderer/overlay-resolver.d.ts.map +1 -1
  80. package/dist/app/renderer/overlay-resolver.js +9 -4
  81. package/dist/app/renderer/prism-renderer.component.d.ts +3 -3
  82. package/dist/app/renderer/prism-renderer.component.d.ts.map +1 -1
  83. package/dist/app/renderer/prism-renderer.component.js +25 -16
  84. package/dist/app/renderer/projectable-content.js +3 -3
  85. package/dist/app/services/prism-canvas.service.d.ts +12 -1
  86. package/dist/app/services/prism-canvas.service.d.ts.map +1 -1
  87. package/dist/app/services/prism-canvas.service.js +23 -5
  88. package/dist/app/services/prism-capture.service.d.ts +24 -50
  89. package/dist/app/services/prism-capture.service.d.ts.map +1 -1
  90. package/dist/app/services/prism-capture.service.js +60 -140
  91. package/dist/app/services/prism-measure.service.d.ts +44 -0
  92. package/dist/app/services/prism-measure.service.d.ts.map +1 -0
  93. package/dist/app/services/prism-measure.service.js +91 -0
  94. package/dist/app/services/prism-panel.service.d.ts +2 -2
  95. package/dist/app/services/prism-panel.service.js +2 -2
  96. package/dist/app/services/prism-persistence.service.js +1 -1
  97. package/dist/app/services/prism-renderer.service.js +1 -1
  98. package/dist/app/services/prism-url-state.service.js +1 -1
  99. package/dist/app/services/prism-variant-bg.service.d.ts +10 -24
  100. package/dist/app/services/prism-variant-bg.service.d.ts.map +1 -1
  101. package/dist/app/services/prism-variant-bg.service.js +10 -24
  102. package/dist/app/shared/prism-metric-badge.component.d.ts +9 -15
  103. package/dist/app/shared/prism-metric-badge.component.d.ts.map +1 -1
  104. package/dist/app/shared/prism-metric-badge.component.js +10 -16
  105. package/dist/app/shell/prism-shell.component.d.ts.map +1 -1
  106. package/dist/app/shell/prism-shell.component.js +12 -9
  107. package/dist/app/sidebar/navigation-decorations.d.ts +4 -4
  108. package/dist/app/sidebar/navigation-decorations.js +2 -2
  109. package/dist/app/sidebar/prism-sidebar.component.d.ts +2 -2
  110. package/dist/app/sidebar/prism-sidebar.component.js +3 -3
  111. package/dist/app/theme/prism-default-theme.js +3 -3
  112. package/dist/app/variant-ribbon/spectrum.d.ts +1 -1
  113. package/dist/app/variant-ribbon/spectrum.js +1 -1
  114. package/dist/app/view-tab-bar/next-view-id.d.ts +1 -1
  115. package/dist/app/view-tab-bar/next-view-id.js +1 -1
  116. package/dist/builder/a11y/a11y-report-reader.d.ts +1 -1
  117. package/dist/builder/a11y/a11y-report-reader.js +1 -1
  118. package/dist/builder/manifest/runtime-manifest.generator.js +6 -2
  119. package/dist/builder/plugin-runner/plugin-runner.js +1 -1
  120. package/dist/builder/scanner/ast-utils.d.ts +46 -2
  121. package/dist/builder/scanner/ast-utils.d.ts.map +1 -1
  122. package/dist/builder/scanner/ast-utils.js +337 -31
  123. package/dist/builder/scanner/class-hierarchy.d.ts +33 -0
  124. package/dist/builder/scanner/class-hierarchy.d.ts.map +1 -0
  125. package/dist/builder/scanner/class-hierarchy.js +87 -0
  126. package/dist/builder/scanner/component.scanner.d.ts +7 -1
  127. package/dist/builder/scanner/component.scanner.d.ts.map +1 -1
  128. package/dist/builder/scanner/component.scanner.js +106 -58
  129. package/dist/builder/scanner/input.extractor.d.ts +7 -4
  130. package/dist/builder/scanner/input.extractor.d.ts.map +1 -1
  131. package/dist/builder/scanner/input.extractor.js +153 -55
  132. package/dist/builder/scanner/manifest.generator.js +6 -2
  133. package/dist/builder/scanner/scanner.d.ts +3 -1
  134. package/dist/builder/scanner/scanner.d.ts.map +1 -1
  135. package/dist/builder/scanner/scanner.js +6 -4
  136. package/dist/builder/scanner/showcase-diagnostics.d.ts +25 -0
  137. package/dist/builder/scanner/showcase-diagnostics.d.ts.map +1 -0
  138. package/dist/builder/scanner/showcase-diagnostics.js +96 -0
  139. package/dist/builder/shared/prism-pipeline.d.ts +1 -1
  140. package/dist/builder/shared/prism-pipeline.d.ts.map +1 -1
  141. package/dist/builder/shared/prism-pipeline.js +12 -14
  142. package/dist/builder/watcher/prism-watcher.js +2 -2
  143. package/dist/decorator/showcase.types.d.ts +11 -9
  144. package/dist/decorator/showcase.types.d.ts.map +1 -1
  145. package/dist/index.js +3 -3
  146. package/dist/plugin/define-config.js +1 -1
  147. package/dist/plugin/index.d.ts.map +1 -1
  148. package/dist/plugin/index.js +5 -9
  149. package/dist/plugin/plugin.types.d.ts +30 -13
  150. package/dist/plugin/plugin.types.d.ts.map +1 -1
  151. package/dist/schematics/migrations/v22-0-cache-dir-rename/index.js +2 -2
  152. package/dist/schematics/ng-add/index.js +4 -4
  153. package/dist/shared/canvas-bg.type.d.ts +5 -6
  154. package/dist/shared/canvas-bg.type.d.ts.map +1 -1
  155. package/dist/shared/canvas-bg.type.js +4 -5
  156. package/dist/shared/measure.type.d.ts +52 -0
  157. package/dist/shared/measure.type.d.ts.map +1 -0
  158. package/dist/shared/measure.type.js +21 -0
  159. package/dist/shared/variant-bg.d.ts +20 -31
  160. package/dist/shared/variant-bg.d.ts.map +1 -1
  161. package/dist/shared/variant-bg.js +20 -31
  162. package/dist/shared/viewport.type.d.ts +15 -19
  163. package/dist/shared/viewport.type.d.ts.map +1 -1
  164. package/dist/shared/viewport.type.js +15 -19
  165. package/dist/transformer/showcase-strip.transformer.js +1 -1
  166. package/package.json +5 -5
@@ -0,0 +1,554 @@
1
+ import { afterNextRender, ChangeDetectionStrategy, Component, computed, DestroyRef, ElementRef, inject, signal } from '@angular/core';
2
+ import { MEASURE_SNAP_TOLERANCE } from '../../shared/measure.type.js';
3
+ import { PrismCanvasService } from '../services/prism-canvas.service.js';
4
+ import { PrismMeasureService } from '../services/prism-measure.service.js';
5
+ import { PrismRendererService } from '../services/prism-renderer.service.js';
6
+ import { constrainDirection, formatMeasure, labelPlacement, measureDistance, quantizeFree, tickEndpoints, toHostSpace, toLocal, toScreen } from './measure-geometry.js';
7
+ import { nudge } from './measure-keyboard.js';
8
+ import { outlineOf, quadCentre, quadLines, quadSpans, quadSummary } from './measure-quad.js';
9
+ import { nearestSnap, readElementBox, snapTargetsFor } from './measure-snap.js';
10
+ import * as i0 from "@angular/core";
11
+ function PrismCanvasMeasureComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
12
+ i0.ɵɵnamespaceSVG();
13
+ i0.ɵɵdomElement(0, "rect", 7);
14
+ } if (rf & 2) {
15
+ const r_r1 = ctx;
16
+ const ctx_r1 = i0.ɵɵnextContext();
17
+ i0.ɵɵclassProp("m-outline--preview", ctx_r1.hover().preview);
18
+ i0.ɵɵattribute("x", r_r1.x)("y", r_r1.y)("width", r_r1.width)("height", r_r1.height);
19
+ } }
20
+ function PrismCanvasMeasureComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
21
+ i0.ɵɵnamespaceSVG();
22
+ i0.ɵɵdomElement(0, "rect", 2);
23
+ } if (rf & 2) {
24
+ const r_r3 = ctx;
25
+ i0.ɵɵattribute("x", r_r3.x)("y", r_r3.y)("width", r_r3.width)("height", r_r3.height);
26
+ } }
27
+ function PrismCanvasMeasureComponent_For_4_For_5_Template(rf, ctx) { if (rf & 1) {
28
+ i0.ɵɵnamespaceSVG();
29
+ i0.ɵɵdomElement(0, "line", 8);
30
+ } if (rf & 2) {
31
+ const e_r4 = ctx.$implicit;
32
+ i0.ɵɵattribute("x1", e_r4[0].x)("y1", e_r4[0].y)("x2", e_r4[1].x)("y2", e_r4[1].y);
33
+ } }
34
+ function PrismCanvasMeasureComponent_For_4_Template(rf, ctx) { if (rf & 1) {
35
+ i0.ɵɵnamespaceSVG();
36
+ i0.ɵɵdomElementStart(0, "g");
37
+ i0.ɵɵdomElement(1, "line")(2, "line")(3, "line");
38
+ i0.ɵɵrepeaterCreate(4, PrismCanvasMeasureComponent_For_4_For_5_Template, 1, 4, ":svg:line", 8, i0.ɵɵrepeaterTrackByIndex);
39
+ i0.ɵɵdomElementEnd();
40
+ } if (rf & 2) {
41
+ const l_r5 = ctx.$implicit;
42
+ i0.ɵɵclassProp("is-pinned", l_r5.pinned);
43
+ i0.ɵɵadvance();
44
+ i0.ɵɵattribute("x1", l_r5.a.x)("y1", l_r5.a.y)("x2", l_r5.b.x)("y2", l_r5.b.y);
45
+ i0.ɵɵadvance();
46
+ i0.ɵɵattribute("x1", l_r5.tickA[0].x)("y1", l_r5.tickA[0].y)("x2", l_r5.tickA[1].x)("y2", l_r5.tickA[1].y);
47
+ i0.ɵɵadvance();
48
+ i0.ɵɵattribute("x1", l_r5.tickB[0].x)("y1", l_r5.tickB[0].y)("x2", l_r5.tickB[1].x)("y2", l_r5.tickB[1].y);
49
+ i0.ɵɵadvance();
50
+ i0.ɵɵrepeater(l_r5.echoes);
51
+ } }
52
+ function PrismCanvasMeasureComponent_For_6_For_5_Template(rf, ctx) { if (rf & 1) {
53
+ i0.ɵɵnamespaceSVG();
54
+ i0.ɵɵdomElement(0, "line", 8);
55
+ } if (rf & 2) {
56
+ const e_r6 = ctx.$implicit;
57
+ i0.ɵɵattribute("x1", e_r6[0].x)("y1", e_r6[0].y)("x2", e_r6[1].x)("y2", e_r6[1].y);
58
+ } }
59
+ function PrismCanvasMeasureComponent_For_6_Template(rf, ctx) { if (rf & 1) {
60
+ i0.ɵɵnamespaceSVG();
61
+ i0.ɵɵdomElementStart(0, "g");
62
+ i0.ɵɵdomElement(1, "line")(2, "line")(3, "line");
63
+ i0.ɵɵrepeaterCreate(4, PrismCanvasMeasureComponent_For_6_For_5_Template, 1, 4, ":svg:line", 8, i0.ɵɵrepeaterTrackByIndex);
64
+ i0.ɵɵdomElementEnd();
65
+ } if (rf & 2) {
66
+ const l_r7 = ctx.$implicit;
67
+ i0.ɵɵclassProp("is-pinned", l_r7.pinned);
68
+ i0.ɵɵadvance();
69
+ i0.ɵɵattribute("x1", l_r7.a.x)("y1", l_r7.a.y)("x2", l_r7.b.x)("y2", l_r7.b.y);
70
+ i0.ɵɵadvance();
71
+ i0.ɵɵattribute("x1", l_r7.tickA[0].x)("y1", l_r7.tickA[0].y)("x2", l_r7.tickA[1].x)("y2", l_r7.tickA[1].y);
72
+ i0.ɵɵadvance();
73
+ i0.ɵɵattribute("x1", l_r7.tickB[0].x)("y1", l_r7.tickB[0].y)("x2", l_r7.tickB[1].x)("y2", l_r7.tickB[1].y);
74
+ i0.ɵɵadvance();
75
+ i0.ɵɵrepeater(l_r7.echoes);
76
+ } }
77
+ function PrismCanvasMeasureComponent_For_9_Template(rf, ctx) { if (rf & 1) {
78
+ i0.ɵɵdomElementStart(0, "span", 9);
79
+ i0.ɵɵtext(1);
80
+ i0.ɵɵdomElementEnd();
81
+ } if (rf & 2) {
82
+ const l_r8 = ctx.$implicit;
83
+ i0.ɵɵstyleProp("left", l_r8.label.x, "px")("top", l_r8.label.y, "px");
84
+ i0.ɵɵclassProp("is-pinned", l_r8.pinned);
85
+ i0.ɵɵadvance();
86
+ i0.ɵɵtextInterpolate1(" ", l_r8.text, " ");
87
+ } }
88
+ function PrismCanvasMeasureComponent_For_11_Template(rf, ctx) { if (rf & 1) {
89
+ i0.ɵɵdomElementStart(0, "span", 9);
90
+ i0.ɵɵtext(1);
91
+ i0.ɵɵdomElementEnd();
92
+ } if (rf & 2) {
93
+ const l_r9 = ctx.$implicit;
94
+ i0.ɵɵstyleProp("left", l_r9.label.x, "px")("top", l_r9.label.y, "px");
95
+ i0.ɵɵclassProp("is-pinned", l_r9.pinned);
96
+ i0.ɵɵadvance();
97
+ i0.ɵɵtextInterpolate1(" ", l_r9.text, " ");
98
+ } }
99
+ function PrismCanvasMeasureComponent_Conditional_13_Template(rf, ctx) { if (rf & 1) {
100
+ i0.ɵɵtext(0);
101
+ } if (rf & 2) {
102
+ const ctx_r1 = i0.ɵɵnextContext();
103
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.hover().summary, " ");
104
+ } }
105
+ function PrismCanvasMeasureComponent_Conditional_14_Template(rf, ctx) { if (rf & 1) {
106
+ i0.ɵɵtext(0);
107
+ } if (rf & 2) {
108
+ const ctx_r1 = i0.ɵɵnextContext();
109
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.lines()[ctx_r1.lines().length - 1].text, " ");
110
+ } }
111
+ /**
112
+ * The topmost element under a screen point that belongs to the specimen.
113
+ *
114
+ * `elementsFromPoint`, the plural one, and the loop are what make this work
115
+ * at all. The overlay host sits above `.demo-wrap` (see the `z-index` in the
116
+ * stylesheet and why it has to be there) and is the one layer that takes
117
+ * pointer events across the whole stage — so the singular `elementFromPoint`
118
+ * answers the overlay itself for every point over the specimen, `contains`
119
+ * then rejects it, and snapping and alt-click anchoring both stop working
120
+ * entirely. Walking the hit list past the overlay's own subtree is what
121
+ * gives the element actually underneath it.
122
+ *
123
+ * The `contains` check stays, and is still not optional: the toolrail and
124
+ * the viewport grips sit *above* the overlay, so they appear in the hit list
125
+ * ahead of anything in the specimen. Only the first hit the rendered root
126
+ * owns is the one the tool may latch onto.
127
+ */
128
+ export function elementUnderPoint(x, y, root) {
129
+ if (!root)
130
+ return null;
131
+ for (const hit of document.elementsFromPoint(x, y)) {
132
+ if (root.contains(hit))
133
+ return hit;
134
+ }
135
+ return null;
136
+ }
137
+ /**
138
+ * A snap's edge as a line segment, crosswise to the edge's axis.
139
+ *
140
+ * The length is deliberately fixed rather than the full edge length: an echo
141
+ * that retraces the whole element edge reads as a second measurement instead
142
+ * of a footnote to the first.
143
+ */
144
+ const ECHO_HALF_LENGTH = 14;
145
+ export function echoFor(point, screen) {
146
+ if (!point.snap)
147
+ return null;
148
+ const horizontal = point.snap.side === 'top' || point.snap.side === 'bottom';
149
+ return horizontal
150
+ ? [
151
+ { x: screen.x - ECHO_HALF_LENGTH, y: screen.y },
152
+ { x: screen.x + ECHO_HALF_LENGTH, y: screen.y }
153
+ ]
154
+ : [
155
+ { x: screen.x, y: screen.y - ECHO_HALF_LENGTH },
156
+ { x: screen.x, y: screen.y + ECHO_HALF_LENGTH }
157
+ ];
158
+ }
159
+ /**
160
+ * Wires the two triggers that tell `lines` its drawn geometry might be
161
+ * stale, and returns how to undo them.
162
+ *
163
+ * A `ResizeObserver` on `stage` and `wrap` covers window resize, panel
164
+ * resize, and the specimen itself changing size. Resize alone is not enough
165
+ * because scrolling moves the specimen without resizing anything — a
166
+ * `ResizeObserver` does not fire on scroll — so `stage` also gets its own
167
+ * `scroll` listener. Dropping either one leaves the lines drawn at a screen
168
+ * position the specimen has since left, which is the worst impression a
169
+ * measuring tool can make.
170
+ *
171
+ * Takes the `ResizeObserver` constructor as a parameter instead of reading
172
+ * the global directly so the wiring stays testable without a real one:
173
+ * jsdom does not implement `ResizeObserver` at all, the same gap
174
+ * `elementsFromPoint` has above.
175
+ */
176
+ export function watchGeometry(stage, wrap, onChange, ResizeObserverCtor = ResizeObserver) {
177
+ const ro = new ResizeObserverCtor(onChange);
178
+ ro.observe(stage);
179
+ if (wrap)
180
+ ro.observe(wrap);
181
+ stage.addEventListener('scroll', onChange);
182
+ return () => {
183
+ ro.disconnect();
184
+ stage.removeEventListener('scroll', onChange);
185
+ };
186
+ }
187
+ const EMPTY_HOVER = { lines: [], summary: '', anchor: null, target: null, preview: false };
188
+ /**
189
+ * Keyboard support here only operates a draft that already exists: nudge,
190
+ * commit, cancel. There is deliberately no keyboard path to *start* one —
191
+ * Spec §10 calls for Tab to step through the specimen's snap targets as the
192
+ * accessible substitute for a pointer-down, and that is not built. Tab is
193
+ * left strictly alone by `onKey` (see its doc, and `nudge`'s) precisely so
194
+ * it stays free for that mechanism once it exists, rather than being
195
+ * repurposed here as a stand-in. The `aria-label` below only promises what
196
+ * this component actually does today; it does not mention Tab, because
197
+ * nothing happens when a user presses it. What the real interaction should
198
+ * be is still undecided — a tool covering the whole canvas cannot simply
199
+ * grab Tab for its own stepping without first deciding how a keyboard user
200
+ * gets back out, and that is a product decision, not one this task makes.
201
+ */
202
+ export class PrismCanvasMeasureComponent {
203
+ el = inject((ElementRef));
204
+ destroyRef = inject(DestroyRef);
205
+ canvas = inject(PrismCanvasService);
206
+ renderer = inject(PrismRendererService);
207
+ measure = inject(PrismMeasureService);
208
+ /**
209
+ * Bumped whenever geometry `lines` depends on might have moved without
210
+ * touching zoom, pins or the draft — see {@link watchGeometry} below.
211
+ *
212
+ * `getBoundingClientRect()` inside `origin()`, `hostRect()`,
213
+ * `specimenCentre()` and `boxOf()` is not itself a reactive signal:
214
+ * without an explicit nudge like this one, nothing would tell `lines` to
215
+ * re-run after a scroll or a resize, and the drawn lines would stay put
216
+ * while the specimen moves out from under them. It covers the host's own
217
+ * rect too, so reading that per evaluation costs no new trigger.
218
+ */
219
+ geometryTick = signal(0, /* @ts-ignore */
220
+ ...(ngDevMode ? [{ debugName: "geometryTick" }] : /* istanbul ignore next */ []));
221
+ /** Origin and zoom the projection runs from, in viewport coordinates. */
222
+ origin() {
223
+ const wrap = this.el.nativeElement.parentElement?.querySelector('.demo-wrap');
224
+ const r = wrap?.getBoundingClientRect();
225
+ return { x: r?.left ?? 0, y: r?.top ?? 0 };
226
+ }
227
+ /**
228
+ * The host's own rect — what every drawn coordinate is measured against.
229
+ *
230
+ * Read once per `lines()` / `hover()` evaluation and handed to
231
+ * {@link toHostSpace}, which carries the full explanation of why the
232
+ * subtraction exists at all.
233
+ */
234
+ hostRect() {
235
+ return this.el.nativeElement.getBoundingClientRect();
236
+ }
237
+ lines = computed(() => {
238
+ const zoom = this.canvas.zoom();
239
+ // Read for the dependency, not the value — the same way
240
+ // prism-canvas-rulers.component.ts forces a dependency on
241
+ // themeService.theme() without using it. This is what makes a
242
+ // scroll or a resize re-run this computed.
243
+ this.geometryTick();
244
+ const host = this.hostRect();
245
+ const origin = this.origin();
246
+ const centre = this.specimenCentre(host);
247
+ const all = [...this.measure.pins().map((m) => ({ m, pinned: true })), ...(this.measure.draft() ? [{ m: this.measure.draft(), pinned: false }] : [])];
248
+ return all.map(({ m, pinned }) => {
249
+ // toScreen answers in viewport coordinates, because that is the
250
+ // space `origin` lives in and the space `pointAt` reads the
251
+ // pointer in. toHostSpace is what turns that into the SVG's own
252
+ // user coordinates; everything downstream — ticks, label, echoes
253
+ // — is derived from the projected points and so follows.
254
+ const a = toHostSpace(toScreen(m.a, origin, zoom), host);
255
+ const b = toHostSpace(toScreen(m.b, origin, zoom), host);
256
+ return {
257
+ a,
258
+ b,
259
+ tickA: tickEndpoints(a, b, 'a'),
260
+ tickB: tickEndpoints(a, b, 'b'),
261
+ label: labelPlacement(a, b, centre),
262
+ text: `${formatMeasure(measureDistance(m.a, m.b))} px`,
263
+ pinned,
264
+ echoes: [echoFor(m.a, a), echoFor(m.b, b)].filter((e) => e !== null)
265
+ };
266
+ });
267
+ }, /* @ts-ignore */
268
+ ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
269
+ /**
270
+ * Everything the alt-hover mode draws, in one evaluation.
271
+ *
272
+ * One computed rather than three because all of it comes out of the same
273
+ * two `getBoundingClientRect()` reads, and splitting it would read the
274
+ * anchor's box once per consumer on every change detection pass.
275
+ */
276
+ hover = computed(() => {
277
+ const anchorEl = this.measure.hoverAnchor();
278
+ const targetEl = this.hoverTarget();
279
+ // Same dependency-only read that `lines` above makes on
280
+ // `geometryTick`. `boxOf` reads `getBoundingClientRect()`, which is
281
+ // not itself reactive: holding the pointer still over an unchanged
282
+ // target does not re-set `hoverTarget` to a new value, so without
283
+ // this a scroll or a resize during that hold would leave the readout
284
+ // stale.
285
+ this.geometryTick();
286
+ // No anchor yet: outline whatever a click would take, so the choice is
287
+ // visible before it is made rather than after. Marked as a prediction
288
+ // so it draws fainter — at equal weight the click would change nothing
289
+ // on screen, the outline simply ceasing to follow the pointer, which is
290
+ // the same missing confirmation the anchor outline exists to provide.
291
+ if (!anchorEl) {
292
+ if (!targetEl)
293
+ return EMPTY_HOVER;
294
+ return { ...EMPTY_HOVER, anchor: outlineOf(this.boxOf(targetEl, this.hostRect())), preview: true };
295
+ }
296
+ const zoom = this.canvas.zoom();
297
+ const host = this.hostRect();
298
+ const a = this.boxOf(anchorEl, host);
299
+ // Spec §4.5: the anchor is outlined dashed from the moment it is
300
+ // set, before anything is hovered. Without it an Alt-click produces
301
+ // no visible change whatsoever — and hovering the anchor back to
302
+ // check cannot confirm it either, because that case deliberately
303
+ // draws no spans. The outline is the only confirmation the anchor
304
+ // registered, and it is also what tells the two ends of a single
305
+ // one-sided reading apart.
306
+ const anchor = outlineOf(a);
307
+ if (!targetEl || anchorEl === targetEl)
308
+ return { ...EMPTY_HOVER, anchor };
309
+ const t = this.boxOf(targetEl, host);
310
+ // `quadSpans` already drops a side with nothing to say, so an anchor
311
+ // and target that merely overlap correctly renders nothing at all —
312
+ // see its doc on `measure-quad.ts`. The DOM-bound half (boxOf) stops
313
+ // here; quadSpans/quadCentre/quadLines/quadSummary are all pure and tested
314
+ // directly in measure-quad.spec.ts.
315
+ const spans = quadSpans(a, t);
316
+ return {
317
+ lines: quadLines(spans, quadCentre(a, t), zoom),
318
+ summary: quadSummary(spans, zoom),
319
+ anchor,
320
+ // Solid against the anchor's dashed outline, per Spec §4.5.
321
+ target: outlineOf(t),
322
+ // The anchor is real by this point, not a prediction.
323
+ preview: false
324
+ };
325
+ }, /* @ts-ignore */
326
+ ...(ngDevMode ? [{ debugName: "hover" }] : /* istanbul ignore next */ []));
327
+ /** The element under the pointer while alt-hovering, `null` otherwise. */
328
+ hoverTarget = signal(null, /* @ts-ignore */
329
+ ...(ngDevMode ? [{ debugName: "hoverTarget" }] : /* istanbul ignore next */ []));
330
+ /**
331
+ * An element's current rectangle as a {@link Box}, already in host
332
+ * space.
333
+ *
334
+ * Projected here rather than at the four call sites downstream: a box
335
+ * that reached `quadSpans` in viewport coordinates would produce correct
336
+ * *distances* — a constant offset cancels in a subtraction — and draw
337
+ * them in the wrong place, which is the half that is actually visible.
338
+ */
339
+ boxOf(element, host) {
340
+ const r = element.getBoundingClientRect();
341
+ const topLeft = toHostSpace({ x: r.left, y: r.top }, host);
342
+ const bottomRight = toHostSpace({ x: r.right, y: r.bottom }, host);
343
+ return { left: topLeft.x, top: topLeft.y, right: bottomRight.x, bottom: bottomRight.y };
344
+ }
345
+ specimenCentre(host) {
346
+ const r = this.renderer.renderedElement()?.getBoundingClientRect();
347
+ return r ? toHostSpace({ x: r.left + r.width / 2, y: r.top + r.height / 2 }, host) : { x: 0, y: 0 };
348
+ }
349
+ /** A pointer point, snapped to the nearest edge, in local coordinates. */
350
+ pointAt(clientX, clientY) {
351
+ const zoom = this.canvas.zoom();
352
+ const origin = this.origin();
353
+ const hit = elementUnderPoint(clientX, clientY, this.renderer.renderedElement());
354
+ const screen = { x: clientX, y: clientY };
355
+ // Every return below is quantized, and the asymmetry inside
356
+ // `quantizeFree` is the reason it is applied here rather than at the
357
+ // call site: only one axis of a snapped point comes from the document,
358
+ // and this is the only place that knows which.
359
+ if (!hit)
360
+ return quantizeFree({ ...toLocal(screen, origin, zoom), snap: null });
361
+ const target = nearestSnap(screen, snapTargetsFor(readElementBox(hit)), MEASURE_SNAP_TOLERANCE);
362
+ if (!target)
363
+ return quantizeFree({ ...toLocal(screen, origin, zoom), snap: null });
364
+ const snapped = target.axis === 'x' ? { x: target.at, y: clientY } : { x: clientX, y: target.at };
365
+ return quantizeFree({ ...toLocal(snapped, origin, zoom), snap: { kind: target.kind, side: target.side, from: hit } });
366
+ }
367
+ /**
368
+ * Keyboard operation of the running draft.
369
+ *
370
+ * `Escape` and `Enter` act regardless of what `nudge` would say about the
371
+ * key; everything else only matters while a draft is open.
372
+ * `preventDefault` is called only on the branches that actually consumed
373
+ * the key — never for `Tab`, which `nudge` returns `null` for and this
374
+ * method then leaves untouched. Swallowing it here would break keyboard
375
+ * navigation of the whole shell for the sake of a tool that, as shipped,
376
+ * has nothing of its own to put Tab to — see `nudge`'s doc.
377
+ */
378
+ onKey(event) {
379
+ if (event.key === 'Escape') {
380
+ this.measure.cancelDraft();
381
+ return;
382
+ }
383
+ if (event.key === 'Enter') {
384
+ this.measure.commitDraft();
385
+ event.preventDefault();
386
+ return;
387
+ }
388
+ const draft = this.measure.draft();
389
+ if (!draft)
390
+ return;
391
+ const moved = nudge(draft.b, event.key, event.shiftKey);
392
+ if (!moved)
393
+ return;
394
+ this.measure.updateDraft({ ...moved, snap: null });
395
+ event.preventDefault();
396
+ }
397
+ constructor() {
398
+ const host = this.el.nativeElement;
399
+ const down = (e) => {
400
+ // Primary button only, and the same failure
401
+ // `prism-resizer.directive.ts` guards against in its own
402
+ // `onMouseDown`: a right-click arms the drag, the context menu
403
+ // then takes the pointer so the matching `pointerup` never
404
+ // arrives, and the draft stays open afterwards with no button
405
+ // held. From there `move`'s `if (!this.measure.draft()) return;`
406
+ // protects nothing any more and the measurement simply follows
407
+ // the cursor. `preventDefault` does not suppress `contextmenu`,
408
+ // so the guard has to be the button check.
409
+ if (e.button !== 0)
410
+ return;
411
+ // Held Alt: fix the element under the pointer as the hover
412
+ // anchor instead of starting a drag measurement.
413
+ if (e.altKey) {
414
+ this.measure.setHoverAnchor(elementUnderPoint(e.clientX, e.clientY, this.renderer.renderedElement()));
415
+ return;
416
+ }
417
+ // Stops the drag from selecting the specimen's text and painting
418
+ // a selection highlight across the very thing being measured.
419
+ // `user-select: none` on the host does not cover it: the
420
+ // selection starts on the specimen underneath, not on the
421
+ // overlay.
422
+ e.preventDefault();
423
+ // …which also suppresses the compatibility `mousedown`, and with
424
+ // it the focus the host would otherwise have received from the
425
+ // click. Without this line the keyboard handling below becomes
426
+ // unreachable for anyone who starts with the pointer, which is
427
+ // everyone — a measurement cannot be started any other way.
428
+ host.focus({ preventScroll: true });
429
+ this.measure.beginDraft(this.pointAt(e.clientX, e.clientY));
430
+ host.setPointerCapture(e.pointerId);
431
+ };
432
+ const move = (e) => {
433
+ // Held Alt: track the element under the pointer as the hover
434
+ // target instead of updating a drag measurement in progress.
435
+ if (e.altKey) {
436
+ this.hoverTarget.set(elementUnderPoint(e.clientX, e.clientY, this.renderer.renderedElement()));
437
+ return;
438
+ }
439
+ this.hoverTarget.set(null);
440
+ const draft = this.measure.draft();
441
+ if (!draft)
442
+ return;
443
+ const point = this.pointAt(e.clientX, e.clientY);
444
+ // Shift pulls the measurement onto the nearest of the eight
445
+ // 45-degree directions, the same service it performs in Photoshop,
446
+ // Illustrator and Figma. It runs on the already-snapped point
447
+ // rather than instead of it, so a latched edge still contributes
448
+ // on whichever axis the constraint preserves — see
449
+ // `constrainDirection`. Releasing Shift frees the drag again
450
+ // immediately, mid-drag included, because nothing about the
451
+ // constraint is stored.
452
+ this.measure.updateDraft(e.shiftKey ? constrainDirection(draft.a, point) : point);
453
+ };
454
+ const up = () => {
455
+ const draft = this.measure.draft();
456
+ if (!draft)
457
+ return;
458
+ // A pointer-down that never moved is a click, not a drag.
459
+ // `beginDraft` seeds `b` with `a`, so the two stay exactly equal
460
+ // until the first `pointermove` — and committing that pins a
461
+ // `0 px` chip the user then has to dismiss by hand. A finished
462
+ // drag being a pin is the design; a bare click is not a drag.
463
+ if (draft.a.x === draft.b.x && draft.a.y === draft.b.y) {
464
+ this.measure.cancelDraft();
465
+ return;
466
+ }
467
+ this.measure.commitDraft();
468
+ };
469
+ // A cancelled pointer — the browser taking it for a scroll or a
470
+ // gesture, the device going away — otherwise leaves the draft open
471
+ // for ever, the same end state the missing button guard above
472
+ // produced by another route.
473
+ const cancel = () => this.measure.cancelDraft();
474
+ host.addEventListener('pointerdown', down);
475
+ host.addEventListener('pointermove', move);
476
+ host.addEventListener('pointerup', up);
477
+ host.addEventListener('pointercancel', cancel);
478
+ this.destroyRef.onDestroy(() => {
479
+ host.removeEventListener('pointerdown', down);
480
+ host.removeEventListener('pointermove', move);
481
+ host.removeEventListener('pointerup', up);
482
+ host.removeEventListener('pointercancel', cancel);
483
+ });
484
+ // Deferred the same way box-model-overlay.component.ts defers its
485
+ // own read of `parentElement`: the host is not reliably attached to
486
+ // its final place in the stage yet while the constructor is running.
487
+ afterNextRender(() => {
488
+ const stage = host.parentElement;
489
+ if (!stage)
490
+ return;
491
+ const wrap = stage.querySelector('.demo-wrap');
492
+ this.destroyRef.onDestroy(watchGeometry(stage, wrap, () => this.geometryTick.update((v) => v + 1)));
493
+ });
494
+ }
495
+ static ɵfac = function PrismCanvasMeasureComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || PrismCanvasMeasureComponent)(); };
496
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: PrismCanvasMeasureComponent, selectors: [["prism-canvas-measure"]], hostAttrs: ["tabindex", "0", "role", "group", "aria-label", "Measure tool. Arrow keys nudge the active point, Shift for a larger step, Enter commits, Escape clears."], hostBindings: function PrismCanvasMeasureComponent_HostBindings(rf, ctx) { if (rf & 1) {
497
+ i0.ɵɵlistener("keydown", function PrismCanvasMeasureComponent_keydown_HostBindingHandler($event) { return ctx.onKey($event); });
498
+ } }, decls: 15, vars: 3, consts: [["aria-hidden", "true", 1, "m-svg"], [1, "m-outline", "m-outline--anchor", 3, "m-outline--preview"], [1, "m-outline", "m-outline--target"], [3, "is-pinned"], [1, "m-labels"], [1, "m-label", 3, "is-pinned", "left", "top"], ["role", "status", "aria-live", "polite", 1, "m-live"], [1, "m-outline", "m-outline--anchor"], [1, "m-echo"], [1, "m-label"]], template: function PrismCanvasMeasureComponent_Template(rf, ctx) { if (rf & 1) {
499
+ i0.ɵɵnamespaceSVG();
500
+ i0.ɵɵdomElementStart(0, "svg", 0);
501
+ i0.ɵɵconditionalCreate(1, PrismCanvasMeasureComponent_Conditional_1_Template, 1, 6, ":svg:rect", 1);
502
+ i0.ɵɵconditionalCreate(2, PrismCanvasMeasureComponent_Conditional_2_Template, 1, 4, ":svg:rect", 2);
503
+ i0.ɵɵrepeaterCreate(3, PrismCanvasMeasureComponent_For_4_Template, 6, 14, ":svg:g", 3, i0.ɵɵrepeaterTrackByIndex);
504
+ i0.ɵɵrepeaterCreate(5, PrismCanvasMeasureComponent_For_6_Template, 6, 14, ":svg:g", 3, i0.ɵɵrepeaterTrackByIndex);
505
+ i0.ɵɵdomElementEnd();
506
+ i0.ɵɵnamespaceHTML();
507
+ i0.ɵɵdomElementStart(7, "div", 4);
508
+ i0.ɵɵrepeaterCreate(8, PrismCanvasMeasureComponent_For_9_Template, 2, 7, "span", 5, i0.ɵɵrepeaterTrackByIndex);
509
+ i0.ɵɵrepeaterCreate(10, PrismCanvasMeasureComponent_For_11_Template, 2, 7, "span", 5, i0.ɵɵrepeaterTrackByIndex);
510
+ i0.ɵɵdomElementEnd();
511
+ i0.ɵɵdomElementStart(12, "span", 6);
512
+ i0.ɵɵconditionalCreate(13, PrismCanvasMeasureComponent_Conditional_13_Template, 1, 1)(14, PrismCanvasMeasureComponent_Conditional_14_Template, 1, 1);
513
+ i0.ɵɵdomElementEnd();
514
+ } if (rf & 2) {
515
+ let tmp_0_0;
516
+ let tmp_1_0;
517
+ i0.ɵɵadvance();
518
+ i0.ɵɵconditional((tmp_0_0 = ctx.hover().anchor) ? 1 : -1, tmp_0_0);
519
+ i0.ɵɵadvance();
520
+ i0.ɵɵconditional((tmp_1_0 = ctx.hover().target) ? 2 : -1, tmp_1_0);
521
+ i0.ɵɵadvance();
522
+ i0.ɵɵrepeater(ctx.lines());
523
+ i0.ɵɵadvance(2);
524
+ i0.ɵɵrepeater(ctx.hover().lines);
525
+ i0.ɵɵadvance(3);
526
+ i0.ɵɵrepeater(ctx.lines());
527
+ i0.ɵɵadvance(2);
528
+ i0.ɵɵrepeater(ctx.hover().lines);
529
+ i0.ɵɵadvance(3);
530
+ i0.ɵɵconditional(ctx.hover().summary ? 13 : ctx.lines().length > 0 ? 14 : -1);
531
+ } }, styles: ["\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n[_nghost-%COMP%] {\n position: absolute;\n inset: 0;\n z-index: 1;\n cursor: crosshair;\n\n \n\n\n\n user-select: none;\n}\n\n.m-svg[_ngcontent-%COMP%], \n.m-labels[_ngcontent-%COMP%] {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n\n\n\n\n\n\n\n\n\n.m-svg[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n}\n\n.m-svg[_ngcontent-%COMP%] line[_ngcontent-%COMP%] {\n stroke: var(--%NS%prism-measure);\n stroke-width: 1;\n stroke-linecap: round;\n}\n\n.m-svg[_ngcontent-%COMP%] .is-pinned[_ngcontent-%COMP%] line[_ngcontent-%COMP%] {\n stroke-opacity: 0.55;\n}\n\n\n\n.m-svg[_ngcontent-%COMP%] .m-echo[_ngcontent-%COMP%] {\n stroke-opacity: 0.34;\n stroke-dasharray: 2 3;\n}\n\n.m-svg[_ngcontent-%COMP%] .is-pinned[_ngcontent-%COMP%] .m-echo[_ngcontent-%COMP%] {\n stroke-opacity: 0.2;\n}\n\n\n\n\n\n\n.m-svg[_ngcontent-%COMP%] .m-outline[_ngcontent-%COMP%] {\n fill: none;\n stroke: var(--%NS%prism-measure);\n stroke-opacity: 0.7;\n stroke-width: 1;\n}\n\n.m-svg[_ngcontent-%COMP%] .m-outline--anchor[_ngcontent-%COMP%] {\n stroke-dasharray: 4 3;\n}\n\n\n\n\n\n\n.m-svg[_ngcontent-%COMP%] .m-outline--preview[_ngcontent-%COMP%] {\n stroke-opacity: 0.35;\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n.m-label[_ngcontent-%COMP%] {\n position: absolute;\n padding: var(--%NS%sp-1) var(--%NS%sp-2);\n font-family: var(--%NS%font-mono);\n font-size: var(--%NS%fs-xs);\n font-variant-numeric: tabular-nums;\n line-height: 1;\n color: var(--%NS%prism-measure);\n white-space: nowrap;\n background: var(--%NS%prism-measure-plate, var(--%NS%prism-stage));\n transform: translate(-50%, -50%);\n}\n\n.m-label.is-pinned[_ngcontent-%COMP%] {\n opacity: 0.62;\n}\n\n\n\n.m-live[_ngcontent-%COMP%] {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n white-space: nowrap;\n clip-path: inset(50%);\n}\n\n[_nghost-%COMP%]:focus-visible {\n outline: 2px solid var(--%NS%prism-measure);\n outline-offset: -2px;\n}"] });
532
+ }
533
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PrismCanvasMeasureComponent, [{
534
+ type: Component,
535
+ args: [{ selector: 'prism-canvas-measure', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
536
+ tabindex: '0',
537
+ // `group`, not `application`. `application` is only justified for a
538
+ // widget with complete custom keyboard handling, because it pulls
539
+ // assistive technology out of browse mode and forwards every
540
+ // keystroke to the widget. The keyboard handling here is explicitly
541
+ // incomplete — see the class doc above: a measurement cannot be
542
+ // started without a pointer — so `application` would trade a screen
543
+ // reader user's browse mode for a key set that can do nothing.
544
+ // `group` is labelled and announced on focus without suppressing
545
+ // browse mode. `application` becomes the right answer the day Spec
546
+ // §10's Tab-stepping through the specimen's snap targets exists,
547
+ // and not before: revisit this line then, together with the
548
+ // `aria-label` below.
549
+ role: 'group',
550
+ 'aria-label': 'Measure tool. Arrow keys nudge the active point, Shift for a larger step, Enter commits, Escape clears.',
551
+ '(keydown)': 'onKey($event)'
552
+ }, template: "<svg\n class=\"m-svg\"\n aria-hidden=\"true\">\n <!-- Spec \u00A74.5. Drawn before the spans so the four measurement lines stay\n the foreground mark and the two outlines read as the context they\n describe. -->\n @if (hover().anchor; as r) {\n <rect\n class=\"m-outline m-outline--anchor\"\n [class.m-outline--preview]=\"hover().preview\"\n [attr.x]=\"r.x\"\n [attr.y]=\"r.y\"\n [attr.width]=\"r.width\"\n [attr.height]=\"r.height\" />\n }\n @if (hover().target; as r) {\n <rect\n class=\"m-outline m-outline--target\"\n [attr.x]=\"r.x\"\n [attr.y]=\"r.y\"\n [attr.width]=\"r.width\"\n [attr.height]=\"r.height\" />\n }\n @for (l of lines(); track $index) {\n <g [class.is-pinned]=\"l.pinned\">\n <line\n [attr.x1]=\"l.a.x\"\n [attr.y1]=\"l.a.y\"\n [attr.x2]=\"l.b.x\"\n [attr.y2]=\"l.b.y\" />\n <line\n [attr.x1]=\"l.tickA[0].x\"\n [attr.y1]=\"l.tickA[0].y\"\n [attr.x2]=\"l.tickA[1].x\"\n [attr.y2]=\"l.tickA[1].y\" />\n <line\n [attr.x1]=\"l.tickB[0].x\"\n [attr.y1]=\"l.tickB[0].y\"\n [attr.x2]=\"l.tickB[1].x\"\n [attr.y2]=\"l.tickB[1].y\" />\n @for (e of l.echoes; track $index) {\n <line\n class=\"m-echo\"\n [attr.x1]=\"e[0].x\"\n [attr.y1]=\"e[0].y\"\n [attr.x2]=\"e[1].x\"\n [attr.y2]=\"e[1].y\" />\n }\n </g>\n }\n @for (l of hover().lines; track $index) {\n <g [class.is-pinned]=\"l.pinned\">\n <line\n [attr.x1]=\"l.a.x\"\n [attr.y1]=\"l.a.y\"\n [attr.x2]=\"l.b.x\"\n [attr.y2]=\"l.b.y\" />\n <line\n [attr.x1]=\"l.tickA[0].x\"\n [attr.y1]=\"l.tickA[0].y\"\n [attr.x2]=\"l.tickA[1].x\"\n [attr.y2]=\"l.tickA[1].y\" />\n <line\n [attr.x1]=\"l.tickB[0].x\"\n [attr.y1]=\"l.tickB[0].y\"\n [attr.x2]=\"l.tickB[1].x\"\n [attr.y2]=\"l.tickB[1].y\" />\n @for (e of l.echoes; track $index) {\n <line\n class=\"m-echo\"\n [attr.x1]=\"e[0].x\"\n [attr.y1]=\"e[0].y\"\n [attr.x2]=\"e[1].x\"\n [attr.y2]=\"e[1].y\" />\n }\n </g>\n }\n</svg>\n\n<div class=\"m-labels\">\n @for (l of lines(); track $index) {\n <span\n class=\"m-label\"\n [class.is-pinned]=\"l.pinned\"\n [style.left.px]=\"l.label.x\"\n [style.top.px]=\"l.label.y\">\n {{ l.text }}\n </span>\n }\n @for (l of hover().lines; track $index) {\n <span\n class=\"m-label\"\n [class.is-pinned]=\"l.pinned\"\n [style.left.px]=\"l.label.x\"\n [style.top.px]=\"l.label.y\">\n {{ l.text }}\n </span>\n }\n</div>\n\n<span\n class=\"m-live\"\n role=\"status\"\n aria-live=\"polite\">\n @if (hover().summary) {\n {{ hover().summary }}\n } @else if (lines().length > 0) {\n {{ lines()[lines().length - 1].text }}\n }\n</span>\n", styles: ["/* The host catches the pointer \u2014 it is the one layer allowed to. Everything\n it draws is exempted from that, or the hit test would land on its own\n lines instead of the specimen. Walking past the host itself is\n elementUnderPoint's job; see its doc.\n\n z-index 1, and both halves of that are load-bearing. It has to be set at\n all because .demo-wrap is position: relative with a transform \u2014 a\n stacking context of its own \u2014 and comes *after* this element in\n prism-renderer.component.html: at z-index auto the two are painted in\n tree order and .demo-wrap wins, hiding every line over the specimen and,\n worse, swallowing the pointerdown that alt-click anchoring needs to land\n on an element. And it has to be the *lowest* of the stage's overlays,\n below the background pill at 2, the rulers, grips and dimension line at\n 4, and the toolrail at 5, because this is the only one of them that\n takes pointer events across the whole stage. Raised past the pill it\n would cover its Reset button \u2014 the failure .vp-grip already documents \u2014\n and raised past the rail it would cover the very button that switches\n the tool off again. */\n:host {\n position: absolute;\n inset: 0;\n z-index: 1;\n cursor: crosshair;\n\n /* A measuring drag across a text-bearing specimen would otherwise paint\n a selection highlight over the thing being measured. `pointerdown`\n calls preventDefault() for the selection that starts underneath; this\n covers a selection that starts on the overlay itself. */\n user-select: none;\n}\n\n.m-svg,\n.m-labels {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n/* An `<svg>` is a replaced element, and for an absolutely positioned replaced\n element CSS takes the used width and height from the intrinsic size \u2014 300x150\n when the element declares none \u2014 instead of resolving them from `inset`. The\n rule above therefore sized this box 300x150 in the stage's top-left corner,\n while `.m-labels`, an ordinary div, stretched to the host as those same\n declarations promised. Since an `<svg>` clips to its own viewport, every line,\n tick, echo and outline beyond that corner was drawn and thrown away, and the\n tool printed numbers with nothing to attach them to. The identical trap is why\n `prism-canvas-rulers.component.ts` sizes its `<canvas>` elements by hand. */\n.m-svg {\n width: 100%;\n height: 100%;\n}\n\n.m-svg line {\n stroke: var(--prism-measure);\n stroke-width: 1;\n stroke-linecap: round;\n}\n\n.m-svg .is-pinned line {\n stroke-opacity: 0.55;\n}\n\n/* The snap echo is a footnote to the measurement, not a second measurement \u2014\n hence dotted and noticeably weaker than the line itself. */\n.m-svg .m-echo {\n stroke-opacity: 0.34;\n stroke-dasharray: 2 3;\n}\n\n.m-svg .is-pinned .m-echo {\n stroke-opacity: 0.2;\n}\n\n/* Spec \u00A74.5: anchor dashed, target solid. Both sit at reduced opacity for\n the same reason the snap echo does \u2014 they say *which two boxes* the four\n numbers are about, and must not compete with the numbers themselves. The\n anchor outline is also the only feedback an Alt-click produces before a\n second element is hovered. */\n.m-svg .m-outline {\n fill: none;\n stroke: var(--prism-measure);\n stroke-opacity: 0.7;\n stroke-width: 1;\n}\n\n.m-svg .m-outline--anchor {\n stroke-dasharray: 4 3;\n}\n\n/* The same dashed shape the anchor takes, at half the weight, while it is\n still only a prediction of what an Alt-click would take. Drawn at equal\n weight the click would produce no visible change \u2014 the outline is already\n there and merely stops following the pointer \u2014 which is the confirmation\n the anchor outline exists to give. */\n.m-svg .m-outline--preview {\n stroke-opacity: 0.35;\n}\n\n/* The number sits outside .demo-wrap and therefore does not scale with\n --zoom. It also never rotates for a diagonal measurement: a tilted number\n is slower to read, and readability is the whole point here.\n\n The plate follows the *ground*, not --prism-stage, and that is the same\n argument canvas-bg.styles.ts makes for --prism-measure itself. On\n bg: 'light' and bg: 'dark' the ground is absolute while --prism-stage\n stays with the theme, so a plate painted in --prism-stage puts the\n re-pointed measurement colour on the other theme's surface: 2.10:1 for a\n light ground under the dark theme, 2.13:1 the other way round. Both are\n documented @Showcase({ bg }) values, so \"always visible\" would have been\n true-in-the-common-case again \u2014 exactly the problem the token override\n exists to prevent. --prism-measure-plate is re-pointed alongside\n --prism-measure in that same file and falls back to --prism-stage\n everywhere else, where the stage *is* the ground. */\n.m-label {\n position: absolute;\n padding: var(--sp-1) var(--sp-2);\n font-family: var(--font-mono);\n font-size: var(--fs-xs);\n font-variant-numeric: tabular-nums;\n line-height: 1;\n color: var(--prism-measure);\n white-space: nowrap;\n background: var(--prism-measure-plate, var(--prism-stage));\n transform: translate(-50%, -50%);\n}\n\n.m-label.is-pinned {\n opacity: 0.62;\n}\n\n/* Visually hidden, not display:none \u2014 a status region only announces to a\n screen reader while it stays in the accessibility tree. */\n.m-live {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n white-space: nowrap;\n clip-path: inset(50%);\n}\n\n:host(:focus-visible) {\n outline: 2px solid var(--prism-measure);\n outline-offset: -2px;\n}\n"] }]
553
+ }], () => [], null); })();
554
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(PrismCanvasMeasureComponent, { className: "PrismCanvasMeasureComponent", filePath: "app/canvas/prism-canvas-measure.component.ts", lineNumber: 163 }); })();
@@ -0,0 +1,23 @@
1
+ import * as i0 from "@angular/core";
2
+ /**
3
+ * The pinned measurements, as a row below the canvas.
4
+ *
5
+ * Not a panel in a corner of the stage: the top overlay band already carries
6
+ * the background pill on the left, the viewport dimension line in the
7
+ * middle and the toolrail on the right. A fourth floating surface there
8
+ * would be the first one to collide with another.
9
+ *
10
+ * A direct child of `.prism-canvas-wrap` and therefore without a capture
11
+ * guard of its own: `CAPTURE_CANVAS_ONLY_SELECTOR` structurally removes
12
+ * every child of the wrap that does not contain the stage. The rule is
13
+ * deliberately written structurally rather than as a list of selectors, so
14
+ * exactly this case works without anyone maintaining it.
15
+ */
16
+ export declare class PrismCanvasPinRowComponent {
17
+ private readonly measure;
18
+ protected readonly chips: import("@angular/core").Signal<string[]>;
19
+ protected remove(index: number): void;
20
+ static ɵfac: i0.ɵɵFactoryDeclaration<PrismCanvasPinRowComponent, never>;
21
+ static ɵcmp: i0.ɵɵComponentDeclaration<PrismCanvasPinRowComponent, "prism-canvas-pin-row", never, {}, {}, never, never, true, never>;
22
+ }
23
+ //# sourceMappingURL=prism-canvas-pin-row.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prism-canvas-pin-row.component.d.ts","sourceRoot":"","sources":["../../../src/app/canvas/prism-canvas-pin-row.component.ts"],"names":[],"mappings":";AAKA;;;;;;;;;;;;;GAaG;AACH,qBAQa,0BAA0B;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IAEvD,SAAS,CAAC,QAAQ,CAAC,KAAK,2CAAoG;IAE5H,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;yCAL5B,0BAA0B;2CAA1B,0BAA0B;CAQtC"}