@ng-prism/plugin-perf 22.6.0 → 22.7.0-beta.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.
@@ -1,4 +1,5 @@
1
1
  import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core';
2
+ import { PrismButtonComponent, PrismPanelToolbarComponent, PrismTabsComponent } from '@ng-prism/core';
2
3
  import { BundleSectionComponent } from './bundle/bundle-section.component.js';
3
4
  import { MemorySectionComponent } from './memory/memory-section.component.js';
4
5
  import { PerfMemoryService } from './memory/perf-memory.service.js';
@@ -6,81 +7,66 @@ import { DEFAULT_THRESHOLDS } from './perf.types.js';
6
7
  import { PerfRenderService } from './render/perf-render.service.js';
7
8
  import { RenderSectionComponent } from './render/render-section.component.js';
8
9
  import * as i0 from "@angular/core";
9
- function PerfPanelComponent_Conditional_9_Conditional_0_Template(rf, ctx) { if (rf & 1) {
10
- const _r2 = i0.ɵɵgetCurrentView();
11
- i0.ɵɵelementStart(0, "button", 10);
12
- i0.ɵɵlistener("click", function PerfPanelComponent_Conditional_9_Conditional_0_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.renderService.stopRecording()); });
13
- i0.ɵɵtext(1, " \u23F8 Pause ");
10
+ function PerfPanelComponent_Conditional_4_Conditional_2_Template(rf, ctx) { if (rf & 1) {
11
+ const _r3 = i0.ɵɵgetCurrentView();
12
+ i0.ɵɵelementStart(0, "button", 9);
13
+ i0.ɵɵlistener("click", function PerfPanelComponent_Conditional_4_Conditional_2_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.renderService.stopRecording()); });
14
+ i0.ɵɵtext(1, " Pause ");
14
15
  i0.ɵɵelementEnd();
16
+ } if (rf & 2) {
17
+ const ctx_r1 = i0.ɵɵnextContext(2);
18
+ i0.ɵɵproperty("title", ctx_r1.profileTitle());
15
19
  } }
16
- function PerfPanelComponent_Conditional_9_Conditional_1_Template(rf, ctx) { if (rf & 1) {
20
+ function PerfPanelComponent_Conditional_4_Conditional_3_Template(rf, ctx) { if (rf & 1) {
17
21
  const _r4 = i0.ɵɵgetCurrentView();
18
- i0.ɵɵelementStart(0, "button", 10);
19
- i0.ɵɵlistener("click", function PerfPanelComponent_Conditional_9_Conditional_1_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.renderService.startRecording()); });
20
- i0.ɵɵtext(1, " \u25B6 Profile ");
21
- i0.ɵɵelementEnd();
22
- } }
23
- function PerfPanelComponent_Conditional_9_Conditional_4_Template(rf, ctx) { if (rf & 1) {
24
- i0.ɵɵelementStart(0, "span", 11);
25
- i0.ɵɵelement(1, "span", 12);
26
- i0.ɵɵtext(2);
22
+ i0.ɵɵelementStart(0, "button", 9);
23
+ i0.ɵɵlistener("click", function PerfPanelComponent_Conditional_4_Conditional_3_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.renderService.startRecording()); });
24
+ i0.ɵɵtext(1, " Profile ");
27
25
  i0.ɵɵelementEnd();
28
26
  } if (rf & 2) {
29
- const ctx_r2 = i0.ɵɵnextContext(2);
30
- i0.ɵɵadvance(2);
31
- i0.ɵɵtextInterpolate1(" live \u00B7 ", ctx_r2.renderService.sampleCount(), " samples ");
27
+ const ctx_r1 = i0.ɵɵnextContext(2);
28
+ i0.ɵɵproperty("title", ctx_r1.profileTitle());
32
29
  } }
33
- function PerfPanelComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
30
+ function PerfPanelComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
34
31
  const _r1 = i0.ɵɵgetCurrentView();
35
- i0.ɵɵconditionalCreate(0, PerfPanelComponent_Conditional_9_Conditional_0_Template, 2, 0, "button", 9)(1, PerfPanelComponent_Conditional_9_Conditional_1_Template, 2, 0, "button", 9);
36
- i0.ɵɵelementStart(2, "button", 10);
37
- i0.ɵɵlistener("click", function PerfPanelComponent_Conditional_9_Template_button_click_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.renderService.clear()); });
38
- i0.ɵɵtext(3, " \u2715 Clear ");
32
+ i0.ɵɵelementStart(0, "button", 7);
33
+ i0.ɵɵlistener("click", function PerfPanelComponent_Conditional_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.renderService.clear()); });
34
+ i0.ɵɵtext(1, " Clear ");
39
35
  i0.ɵɵelementEnd();
40
- i0.ɵɵconditionalCreate(4, PerfPanelComponent_Conditional_9_Conditional_4_Template, 3, 1, "span", 11);
36
+ i0.ɵɵconditionalCreate(2, PerfPanelComponent_Conditional_4_Conditional_2_Template, 2, 1, "button", 8)(3, PerfPanelComponent_Conditional_4_Conditional_3_Template, 2, 1, "button", 8);
41
37
  } if (rf & 2) {
42
- const ctx_r2 = i0.ɵɵnextContext();
43
- i0.ɵɵconditional(ctx_r2.renderService.isRecording() ? 0 : 1);
44
- i0.ɵɵadvance(4);
45
- i0.ɵɵconditional(ctx_r2.renderService.isRecording() ? 4 : -1);
38
+ const ctx_r1 = i0.ɵɵnextContext();
39
+ i0.ɵɵadvance(2);
40
+ i0.ɵɵconditional(ctx_r1.renderService.isRecording() ? 2 : 3);
46
41
  } }
47
- function PerfPanelComponent_Conditional_10_Template(rf, ctx) { if (rf & 1) {
42
+ function PerfPanelComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
48
43
  const _r5 = i0.ɵɵgetCurrentView();
49
- i0.ɵɵelementStart(0, "button", 10);
50
- i0.ɵɵlistener("click", function PerfPanelComponent_Conditional_10_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r5); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.captureMemorySnapshots()); });
51
- i0.ɵɵtext(1, " \u25B6 Snapshot ");
44
+ i0.ɵɵelementStart(0, "button", 7);
45
+ i0.ɵɵlistener("click", function PerfPanelComponent_Conditional_5_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.memoryService.clear()); });
46
+ i0.ɵɵtext(1, " Clear ");
52
47
  i0.ɵɵelementEnd();
53
48
  i0.ɵɵelementStart(2, "button", 10);
54
- i0.ɵɵlistener("click", function PerfPanelComponent_Conditional_10_Template_button_click_2_listener() { i0.ɵɵrestoreView(_r5); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.memoryService.clear()); });
55
- i0.ɵɵtext(3, " \u2715 Clear ");
49
+ i0.ɵɵlistener("click", function PerfPanelComponent_Conditional_5_Template_button_click_2_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.captureMemorySnapshots()); });
50
+ i0.ɵɵtext(3, " Snapshot ");
56
51
  i0.ɵɵelementEnd();
57
52
  } }
58
- function PerfPanelComponent_Conditional_11_Template(rf, ctx) { if (rf & 1) {
59
- i0.ɵɵelementStart(0, "span", 4);
60
- i0.ɵɵtext(1);
61
- i0.ɵɵelementEnd();
53
+ function PerfPanelComponent_Case_7_Template(rf, ctx) { if (rf & 1) {
54
+ i0.ɵɵelement(0, "perf-bundle-section", 4);
62
55
  } if (rf & 2) {
63
- const ctx_r2 = i0.ɵɵnextContext();
64
- i0.ɵɵadvance();
65
- i0.ɵɵtextInterpolate1(" ", ctx_r2.renderService.cdRunCount(), " CD runs ");
56
+ const ctx_r1 = i0.ɵɵnextContext();
57
+ i0.ɵɵproperty("metrics", ctx_r1.bundleMetrics())("warnKb", ctx_r1.thresholds.bundleWarnKb)("critKb", ctx_r1.thresholds.bundleCritKb);
66
58
  } }
67
- function PerfPanelComponent_Case_13_Template(rf, ctx) { if (rf & 1) {
68
- i0.ɵɵelement(0, "perf-bundle-section", 6);
59
+ function PerfPanelComponent_Case_8_Template(rf, ctx) { if (rf & 1) {
60
+ i0.ɵɵelement(0, "perf-render-section", 5);
69
61
  } if (rf & 2) {
70
- const ctx_r2 = i0.ɵɵnextContext();
71
- i0.ɵɵproperty("metrics", ctx_r2.bundleMetrics())("warnKb", ctx_r2.thresholds.bundleWarnKb)("critKb", ctx_r2.thresholds.bundleCritKb);
62
+ const ctx_r1 = i0.ɵɵnextContext();
63
+ i0.ɵɵproperty("renderService", ctx_r1.renderService)("warnMs", ctx_r1.thresholds.renderWarnMs)("critMs", ctx_r1.thresholds.renderCritMs);
72
64
  } }
73
- function PerfPanelComponent_Case_14_Template(rf, ctx) { if (rf & 1) {
74
- i0.ɵɵelement(0, "perf-render-section", 7);
65
+ function PerfPanelComponent_Case_9_Template(rf, ctx) { if (rf & 1) {
66
+ i0.ɵɵelement(0, "perf-memory-section", 6);
75
67
  } if (rf & 2) {
76
- const ctx_r2 = i0.ɵɵnextContext();
77
- i0.ɵɵproperty("renderService", ctx_r2.renderService)("warnMs", ctx_r2.thresholds.renderWarnMs)("critMs", ctx_r2.thresholds.renderCritMs);
78
- } }
79
- function PerfPanelComponent_Case_15_Template(rf, ctx) { if (rf & 1) {
80
- i0.ɵɵelement(0, "perf-memory-section", 8);
81
- } if (rf & 2) {
82
- const ctx_r2 = i0.ɵɵnextContext();
83
- i0.ɵɵproperty("memoryService", ctx_r2.memoryService);
68
+ const ctx_r1 = i0.ɵɵnextContext();
69
+ i0.ɵɵproperty("memoryService", ctx_r1.memoryService);
84
70
  } }
85
71
  export class PerfPanelComponent {
86
72
  activeComponent = input(null, /* @ts-ignore */
@@ -90,6 +76,28 @@ export class PerfPanelComponent {
90
76
  renderService = PerfRenderService.getInstance();
91
77
  memoryService = PerfMemoryService.getInstance();
92
78
  thresholds = DEFAULT_THRESHOLDS;
79
+ tabs = [
80
+ { id: 'bundle', label: 'Bundle' },
81
+ { id: 'render', label: 'Render' },
82
+ { id: 'memory', label: 'Memory' }
83
+ ];
84
+ selectTab(id) {
85
+ if (id === 'bundle' || id === 'render' || id === 'memory')
86
+ this.activeTab.set(id);
87
+ }
88
+ /** Recording state and change-detection runs: meta for the tooltip, not for the toolbar. */
89
+ profileTitle = computed(() => {
90
+ const parts = [];
91
+ if (this.renderService.isRecording())
92
+ parts.push(`Live · ${this.renderService.sampleCount()} samples`);
93
+ else
94
+ parts.push('Record render times');
95
+ const cd = this.renderService.cdRunCount();
96
+ if (cd > 0)
97
+ parts.push(`${cd} change detection runs`);
98
+ return parts.join(' · ');
99
+ }, /* @ts-ignore */
100
+ ...(ngDevMode ? [{ debugName: "profileTitle" }] : /* istanbul ignore next */ []));
93
101
  captureMemorySnapshots() {
94
102
  this.memoryService.takeSnapshot('before-create');
95
103
  setTimeout(() => this.memoryService.takeSnapshot('after-create'), 50);
@@ -100,47 +108,31 @@ export class PerfPanelComponent {
100
108
  }, /* @ts-ignore */
101
109
  ...(ngDevMode ? [{ debugName: "bundleMetrics" }] : /* istanbul ignore next */ []));
102
110
  static ɵfac = function PerfPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || PerfPanelComponent)(); };
103
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: PerfPanelComponent, selectors: [["prism-perf-panel"]], inputs: { activeComponent: [1, "activeComponent"] }, decls: 16, vars: 10, consts: [[1, "prism-perf-panel"], [1, "prism-subtabs"], [1, "prism-st-tab", 3, "click"], [1, "prism-subtabs-actions"], [1, "prism-perf-cd-badge"], [1, "prism-perf-content"], [3, "metrics", "warnKb", "critKb"], [3, "renderService", "warnMs", "critMs"], [3, "memoryService"], [1, "prism-tool-btn"], [1, "prism-tool-btn", 3, "click"], [1, "prism-perf-status"], [1, "prism-perf-status-dot"]], template: function PerfPanelComponent_Template(rf, ctx) { if (rf & 1) {
104
- i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "button", 2);
105
- i0.ɵɵlistener("click", function PerfPanelComponent_Template_button_click_2_listener() { return ctx.activeTab.set("bundle"); });
106
- i0.ɵɵtext(3, " Bundle ");
107
- i0.ɵɵelementEnd();
108
- i0.ɵɵelementStart(4, "button", 2);
109
- i0.ɵɵlistener("click", function PerfPanelComponent_Template_button_click_4_listener() { return ctx.activeTab.set("render"); });
110
- i0.ɵɵtext(5, " Render ");
111
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: PerfPanelComponent, selectors: [["prism-perf-panel"]], inputs: { activeComponent: [1, "activeComponent"] }, decls: 10, vars: 5, consts: [[1, "prism-perf-panel"], ["toolbarStart", "", "size", "sm", "ariaLabel", "Performance views", 3, "activeChange", "items", "active"], ["toolbarEnd", "", 1, "prism-perf-actions"], [1, "prism-perf-content"], [3, "metrics", "warnKb", "critKb"], [3, "renderService", "warnMs", "critMs"], [3, "memoryService"], ["prismButton", "", "variant", "secondary", 3, "click"], ["prismButton", "", "variant", "primary", 3, "title"], ["prismButton", "", "variant", "primary", 3, "click", "title"], ["prismButton", "", "variant", "primary", 3, "click"]], template: function PerfPanelComponent_Template(rf, ctx) { if (rf & 1) {
112
+ i0.ɵɵelementStart(0, "div", 0)(1, "prism-panel-toolbar")(2, "prism-tabs", 1);
113
+ i0.ɵɵlistener("activeChange", function PerfPanelComponent_Template_prism_tabs_activeChange_2_listener($event) { return ctx.selectTab($event); });
111
114
  i0.ɵɵelementEnd();
112
- i0.ɵɵelementStart(6, "button", 2);
113
- i0.ɵɵlistener("click", function PerfPanelComponent_Template_button_click_6_listener() { return ctx.activeTab.set("memory"); });
114
- i0.ɵɵtext(7, " Memory ");
115
- i0.ɵɵelementEnd();
116
- i0.ɵɵelementStart(8, "div", 3);
117
- i0.ɵɵconditionalCreate(9, PerfPanelComponent_Conditional_9_Template, 5, 2);
118
- i0.ɵɵconditionalCreate(10, PerfPanelComponent_Conditional_10_Template, 4, 0);
119
- i0.ɵɵconditionalCreate(11, PerfPanelComponent_Conditional_11_Template, 2, 1, "span", 4);
115
+ i0.ɵɵelementStart(3, "div", 2);
116
+ i0.ɵɵconditionalCreate(4, PerfPanelComponent_Conditional_4_Template, 4, 1);
117
+ i0.ɵɵconditionalCreate(5, PerfPanelComponent_Conditional_5_Template, 4, 0);
120
118
  i0.ɵɵelementEnd()();
121
- i0.ɵɵelementStart(12, "div", 5);
122
- i0.ɵɵconditionalCreate(13, PerfPanelComponent_Case_13_Template, 1, 3, "perf-bundle-section", 6)(14, PerfPanelComponent_Case_14_Template, 1, 3, "perf-render-section", 7)(15, PerfPanelComponent_Case_15_Template, 1, 1, "perf-memory-section", 8);
119
+ i0.ɵɵelementStart(6, "div", 3);
120
+ i0.ɵɵconditionalCreate(7, PerfPanelComponent_Case_7_Template, 1, 3, "perf-bundle-section", 4)(8, PerfPanelComponent_Case_8_Template, 1, 3, "perf-render-section", 5)(9, PerfPanelComponent_Case_9_Template, 1, 1, "perf-memory-section", 6);
123
121
  i0.ɵɵelementEnd()();
124
122
  } if (rf & 2) {
125
- let tmp_6_0;
126
- i0.ɵɵadvance(2);
127
- i0.ɵɵclassProp("prism-st-tab--active", ctx.activeTab() === "bundle");
123
+ let tmp_4_0;
128
124
  i0.ɵɵadvance(2);
129
- i0.ɵɵclassProp("prism-st-tab--active", ctx.activeTab() === "render");
125
+ i0.ɵɵproperty("items", ctx.tabs)("active", ctx.activeTab());
130
126
  i0.ɵɵadvance(2);
131
- i0.ɵɵclassProp("prism-st-tab--active", ctx.activeTab() === "memory");
132
- i0.ɵɵadvance(3);
133
- i0.ɵɵconditional(ctx.activeTab() === "render" ? 9 : -1);
134
- i0.ɵɵadvance();
135
- i0.ɵɵconditional(ctx.activeTab() === "memory" ? 10 : -1);
127
+ i0.ɵɵconditional(ctx.activeTab() === "render" ? 4 : -1);
136
128
  i0.ɵɵadvance();
137
- i0.ɵɵconditional(ctx.renderService.cdRunCount() > 0 ? 11 : -1);
129
+ i0.ɵɵconditional(ctx.activeTab() === "memory" ? 5 : -1);
138
130
  i0.ɵɵadvance(2);
139
- i0.ɵɵconditional((tmp_6_0 = ctx.activeTab()) === "bundle" ? 13 : tmp_6_0 === "render" ? 14 : tmp_6_0 === "memory" ? 15 : -1);
140
- } }, dependencies: [BundleSectionComponent, RenderSectionComponent, MemorySectionComponent], styles: ["[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.prism-perf-panel[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.prism-subtabs[_ngcontent-%COMP%] {\n position: sticky;\n top: 0;\n z-index: 2;\n display: flex;\n gap: 2px;\n padding: 10px 20px 0;\n background: var(--%NS%prism-bg-elevated);\n border-bottom: 1px solid var(--%NS%prism-border);\n}\n\n.prism-st-tab[_ngcontent-%COMP%] {\n position: relative;\n display: flex;\n gap: 6px;\n align-items: center;\n padding: 7px 12px;\n font-family: var(--%NS%font-sans);\n font-size: 12px;\n font-weight: 500;\n color: var(--%NS%prism-text-muted);\n cursor: pointer;\n background: transparent;\n border: none;\n border-radius: 6px 6px 0 0;\n transition: color var(--%NS%dur-fast);\n}\n\n.prism-st-tab[_ngcontent-%COMP%]:hover {\n color: var(--%NS%prism-text-2);\n}\n\n.prism-st-tab--active[_ngcontent-%COMP%] {\n color: var(--%NS%prism-text);\n}\n\n.prism-st-tab--%NS%active[_ngcontent-%COMP%]::after {\n position: absolute;\n right: 8px;\n bottom: -1px;\n left: 8px;\n height: 2px;\n content: '';\n background: var(--%NS%prism-primary);\n border-radius: 1px;\n}\n\n.prism-subtabs-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n align-items: center;\n margin-left: auto;\n}\n\n.prism-perf-cd-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 24px;\n padding: 0 8px;\n font-family: var(--%NS%font-mono, 'JetBrains Mono', monospace);\n font-size: 11px;\n color: var(--%NS%prism-primary);\n background: color-mix(in srgb, var(--%NS%prism-primary) 12%, transparent);\n border: 1px solid color-mix(in srgb, var(--%NS%prism-primary) 25%, transparent);\n border-radius: 5px;\n}\n\n.prism-tool-btn[_ngcontent-%COMP%] {\n display: flex;\n gap: 5px;\n align-items: center;\n height: 24px;\n padding: 0 9px;\n font-family: var(--%NS%font-sans);\n font-size: 11.5px;\n font-weight: 500;\n color: var(--%NS%prism-text-muted);\n cursor: pointer;\n background: none;\n border: none;\n border-radius: 5px;\n transition: all 0.12s;\n}\n\n.prism-tool-btn[_ngcontent-%COMP%]:hover {\n color: var(--%NS%prism-text);\n background: color-mix(in srgb, var(--%NS%prism-primary) 8%, transparent);\n}\n\n.prism-perf-status[_ngcontent-%COMP%] {\n display: flex;\n gap: 5px;\n align-items: center;\n font-family: var(--%NS%font-mono);\n font-size: 11px;\n color: var(--%NS%prism-text-muted);\n}\n\n.prism-perf-status-dot[_ngcontent-%COMP%] {\n display: inline-block;\n width: 5px;\n height: 5px;\n background: var(--%NS%prism-success);\n border-radius: 50%;\n box-shadow: 0 0 6px var(--%NS%prism-success);\n animation: _ngcontent-%COMP%_prism-perf-pulse 2s ease-in-out infinite;\n}\n\n@keyframes _ngcontent-%COMP%_prism-perf-pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n\n 50% {\n opacity: 0.4;\n }\n}\n\n.prism-perf-content[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}"] });
131
+ i0.ɵɵconditional((tmp_4_0 = ctx.activeTab()) === "bundle" ? 7 : tmp_4_0 === "render" ? 8 : tmp_4_0 === "memory" ? 9 : -1);
132
+ } }, dependencies: [BundleSectionComponent, RenderSectionComponent, MemorySectionComponent, PrismPanelToolbarComponent, PrismTabsComponent, PrismButtonComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n height: 100%;\n}\n\n.prism-perf-panel[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.prism-perf-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n.prism-perf-content[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: auto;\n}"] });
141
133
  }
142
134
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PerfPanelComponent, [{
143
135
  type: Component,
144
- args: [{ selector: 'prism-perf-panel', standalone: true, imports: [BundleSectionComponent, RenderSectionComponent, MemorySectionComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"prism-perf-panel\">\n <div class=\"prism-subtabs\">\n <button\n class=\"prism-st-tab\"\n [class.prism-st-tab--active]=\"activeTab() === 'bundle'\"\n (click)=\"activeTab.set('bundle')\">\n Bundle\n </button>\n <button\n class=\"prism-st-tab\"\n [class.prism-st-tab--active]=\"activeTab() === 'render'\"\n (click)=\"activeTab.set('render')\">\n Render\n </button>\n <button\n class=\"prism-st-tab\"\n [class.prism-st-tab--active]=\"activeTab() === 'memory'\"\n (click)=\"activeTab.set('memory')\">\n Memory\n </button>\n <div class=\"prism-subtabs-actions\">\n @if (activeTab() === 'render') {\n @if (renderService.isRecording()) {\n <button\n class=\"prism-tool-btn\"\n (click)=\"renderService.stopRecording()\">\n &#9208; Pause\n </button>\n } @else {\n <button\n class=\"prism-tool-btn\"\n (click)=\"renderService.startRecording()\">\n &#9654; Profile\n </button>\n }\n <button\n class=\"prism-tool-btn\"\n (click)=\"renderService.clear()\">\n &#10005; Clear\n </button>\n @if (renderService.isRecording()) {\n <span class=\"prism-perf-status\"> <span class=\"prism-perf-status-dot\"></span> live &middot; {{ renderService.sampleCount() }} samples </span>\n }\n }\n @if (activeTab() === 'memory') {\n <button\n class=\"prism-tool-btn\"\n (click)=\"captureMemorySnapshots()\">\n &#9654; Snapshot\n </button>\n <button\n class=\"prism-tool-btn\"\n (click)=\"memoryService.clear()\">\n &#10005; Clear\n </button>\n }\n @if (renderService.cdRunCount() > 0) {\n <span class=\"prism-perf-cd-badge\"> {{ renderService.cdRunCount() }} CD runs </span>\n }\n </div>\n </div>\n\n <div class=\"prism-perf-content\">\n @switch (activeTab()) {\n @case ('bundle') {\n <perf-bundle-section\n [metrics]=\"bundleMetrics()\"\n [warnKb]=\"thresholds.bundleWarnKb\"\n [critKb]=\"thresholds.bundleCritKb\" />\n }\n @case ('render') {\n <perf-render-section\n [renderService]=\"renderService\"\n [warnMs]=\"thresholds.renderWarnMs\"\n [critMs]=\"thresholds.renderCritMs\" />\n }\n @case ('memory') {\n <perf-memory-section [memoryService]=\"memoryService\" />\n }\n }\n </div>\n</div>\n", styles: [":host {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.prism-perf-panel {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.prism-subtabs {\n position: sticky;\n top: 0;\n z-index: 2;\n display: flex;\n gap: 2px;\n padding: 10px 20px 0;\n background: var(--prism-bg-elevated);\n border-bottom: 1px solid var(--prism-border);\n}\n\n.prism-st-tab {\n position: relative;\n display: flex;\n gap: 6px;\n align-items: center;\n padding: 7px 12px;\n font-family: var(--font-sans);\n font-size: 12px;\n font-weight: 500;\n color: var(--prism-text-muted);\n cursor: pointer;\n background: transparent;\n border: none;\n border-radius: 6px 6px 0 0;\n transition: color var(--dur-fast);\n}\n\n.prism-st-tab:hover {\n color: var(--prism-text-2);\n}\n\n.prism-st-tab--active {\n color: var(--prism-text);\n}\n\n.prism-st-tab--active::after {\n position: absolute;\n right: 8px;\n bottom: -1px;\n left: 8px;\n height: 2px;\n content: '';\n background: var(--prism-primary);\n border-radius: 1px;\n}\n\n.prism-subtabs-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n margin-left: auto;\n}\n\n.prism-perf-cd-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 24px;\n padding: 0 8px;\n font-family: var(--font-mono, 'JetBrains Mono', monospace);\n font-size: 11px;\n color: var(--prism-primary);\n background: color-mix(in srgb, var(--prism-primary) 12%, transparent);\n border: 1px solid color-mix(in srgb, var(--prism-primary) 25%, transparent);\n border-radius: 5px;\n}\n\n.prism-tool-btn {\n display: flex;\n gap: 5px;\n align-items: center;\n height: 24px;\n padding: 0 9px;\n font-family: var(--font-sans);\n font-size: 11.5px;\n font-weight: 500;\n color: var(--prism-text-muted);\n cursor: pointer;\n background: none;\n border: none;\n border-radius: 5px;\n transition: all 0.12s;\n}\n\n.prism-tool-btn:hover {\n color: var(--prism-text);\n background: color-mix(in srgb, var(--prism-primary) 8%, transparent);\n}\n\n.prism-perf-status {\n display: flex;\n gap: 5px;\n align-items: center;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--prism-text-muted);\n}\n\n.prism-perf-status-dot {\n display: inline-block;\n width: 5px;\n height: 5px;\n background: var(--prism-success);\n border-radius: 50%;\n box-shadow: 0 0 6px var(--prism-success);\n animation: prism-perf-pulse 2s ease-in-out infinite;\n}\n\n@keyframes prism-perf-pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n\n 50% {\n opacity: 0.4;\n }\n}\n\n.prism-perf-content {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n"] }]
136
+ args: [{ selector: 'prism-perf-panel', standalone: true, imports: [BundleSectionComponent, RenderSectionComponent, MemorySectionComponent, PrismPanelToolbarComponent, PrismTabsComponent, PrismButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"prism-perf-panel\">\n <prism-panel-toolbar>\n <prism-tabs\n toolbarStart\n size=\"sm\"\n ariaLabel=\"Performance views\"\n [items]=\"tabs\"\n [active]=\"activeTab()\"\n (activeChange)=\"selectTab($event)\" />\n <div\n toolbarEnd\n class=\"prism-perf-actions\">\n @if (activeTab() === 'render') {\n <button\n prismButton\n variant=\"secondary\"\n (click)=\"renderService.clear()\">\n Clear\n </button>\n @if (renderService.isRecording()) {\n <button\n prismButton\n variant=\"primary\"\n [title]=\"profileTitle()\"\n (click)=\"renderService.stopRecording()\">\n Pause\n </button>\n } @else {\n <button\n prismButton\n variant=\"primary\"\n [title]=\"profileTitle()\"\n (click)=\"renderService.startRecording()\">\n Profile\n </button>\n }\n }\n @if (activeTab() === 'memory') {\n <button\n prismButton\n variant=\"secondary\"\n (click)=\"memoryService.clear()\">\n Clear\n </button>\n <button\n prismButton\n variant=\"primary\"\n (click)=\"captureMemorySnapshots()\">\n Snapshot\n </button>\n }\n </div>\n </prism-panel-toolbar>\n\n <div class=\"prism-perf-content\">\n @switch (activeTab()) {\n @case ('bundle') {\n <perf-bundle-section\n [metrics]=\"bundleMetrics()\"\n [warnKb]=\"thresholds.bundleWarnKb\"\n [critKb]=\"thresholds.bundleCritKb\" />\n }\n @case ('render') {\n <perf-render-section\n [renderService]=\"renderService\"\n [warnMs]=\"thresholds.renderWarnMs\"\n [critMs]=\"thresholds.renderCritMs\" />\n }\n @case ('memory') {\n <perf-memory-section [memoryService]=\"memoryService\" />\n }\n }\n </div>\n</div>\n", styles: [":host {\n display: block;\n height: 100%;\n}\n\n.prism-perf-panel {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.prism-perf-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n.prism-perf-content {\n flex: 1;\n min-height: 0;\n overflow: auto;\n}\n"] }]
145
137
  }], null, { activeComponent: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeComponent", required: false }] }] }); })();
146
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(PerfPanelComponent, { className: "PerfPanelComponent", filePath: "perf-panel.component.ts", lineNumber: 20 }); })();
138
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(PerfPanelComponent, { className: "PerfPanelComponent", filePath: "perf-panel.component.ts", lineNumber: 21 }); })();
@@ -0,0 +1,12 @@
1
+ import { type StatusTone } from '@ng-prism/core/plugin';
2
+ /**
3
+ * A perf figure graded against its budget, lower being better: within the
4
+ * warning bound it is OK, up to the critical bound a warning, above it critical.
5
+ * Same rule as every other key figure (`statusFor`), so the card's colour and
6
+ * its label cannot disagree.
7
+ */
8
+ export declare function perfStatus(value: number, warn: number, crit: number): {
9
+ tone: StatusTone;
10
+ label: 'OK' | 'Warning' | 'Critical';
11
+ };
12
+ //# sourceMappingURL=perf-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"perf-status.d.ts","sourceRoot":"","sources":["../src/perf-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAQnE;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,IAAI,GAAG,SAAS,GAAG,UAAU,CAAA;CAAE,CAIhI"}
@@ -0,0 +1,16 @@
1
+ import { statusFor } from '@ng-prism/core/plugin';
2
+ const LABEL = {
3
+ ok: 'OK',
4
+ warn: 'Warning',
5
+ danger: 'Critical'
6
+ };
7
+ /**
8
+ * A perf figure graded against its budget, lower being better: within the
9
+ * warning bound it is OK, up to the critical bound a warning, above it critical.
10
+ * Same rule as every other key figure (`statusFor`), so the card's colour and
11
+ * its label cannot disagree.
12
+ */
13
+ export function perfStatus(value, warn, crit) {
14
+ const tone = statusFor(value, { ok: warn, warn: crit, direction: 'lower' });
15
+ return { tone, label: LABEL[tone] };
16
+ }
@@ -1,12 +1,17 @@
1
+ import { type BadgeTone } from '@ng-prism/core';
1
2
  import type { PerfRenderService } from './perf-render.service.js';
2
3
  import * as i0 from "@angular/core";
3
4
  export declare class RenderSectionComponent {
4
5
  readonly renderService: import("@angular/core").InputSignal<PerfRenderService>;
5
6
  readonly warnMs: import("@angular/core").InputSignal<number>;
6
7
  readonly critMs: import("@angular/core").InputSignal<number>;
7
- readonly initialClass: import("@angular/core").Signal<"" | "crit" | "warn">;
8
8
  formatMs(value: number | null): string;
9
- colorFor(value: number | null): string;
9
+ /** A measured render time graded against the budget; `null` when there is nothing to grade. */
10
+ statusOf(value: number | null): {
11
+ tone: BadgeTone;
12
+ label: string;
13
+ } | null;
14
+ budgetTitle(what: string): string;
10
15
  static ɵfac: i0.ɵɵFactoryDeclaration<RenderSectionComponent, never>;
11
16
  static ɵcmp: i0.ɵɵComponentDeclaration<RenderSectionComponent, "perf-render-section", never, { "renderService": { "alias": "renderService"; "required": true; "isSignal": true; }; "warnMs": { "alias": "warnMs"; "required": false; "isSignal": true; }; "critMs": { "alias": "critMs"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
12
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"render-section.component.d.ts","sourceRoot":"","sources":["../../src/render/render-section.component.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;;AAGlE,qBAQa,sBAAsB;IAC/B,QAAQ,CAAC,aAAa,yDAAuC;IAC7D,QAAQ,CAAC,MAAM,8CAAY;IAC3B,QAAQ,CAAC,MAAM,8CAAa;IAE5B,QAAQ,CAAC,YAAY,uDAOlB;IAEH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;IAKtC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;yCAnB7B,sBAAsB;2CAAtB,sBAAsB;CAyBlC"}
1
+ {"version":3,"file":"render-section.component.d.ts","sourceRoot":"","sources":["../../src/render/render-section.component.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAA2C,MAAM,gBAAgB,CAAC;AAEzF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;;AAGlE,qBAQa,sBAAsB;IAC/B,QAAQ,CAAC,aAAa,yDAAuC;IAC7D,QAAQ,CAAC,MAAM,8CAAY;IAC3B,QAAQ,CAAC,MAAM,8CAAa;IAE5B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;IAKtC,+FAA+F;IAC/F,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAOzE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;yCAlBxB,sBAAsB;2CAAtB,sBAAsB;CAqBlC"}
@@ -1,109 +1,53 @@
1
- import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
1
+ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
2
+ import { PrismMetricCardComponent, toneForStatus } from '@ng-prism/core';
3
+ import { perfStatus } from '../perf-status.js';
2
4
  import { SparklineComponent } from './sparkline.component.js';
3
5
  import * as i0 from "@angular/core";
4
- function RenderSectionComponent_Conditional_0_Conditional_0_Conditional_43_Template(rf, ctx) { if (rf & 1) {
5
- i0.ɵɵelementStart(0, "div", 7)(1, "div", 8)(2, "div", 3)(3, "span", 4);
6
- i0.ɵɵtext(4);
6
+ function RenderSectionComponent_Conditional_0_Conditional_0_Conditional_6_Template(rf, ctx) { if (rf & 1) {
7
+ i0.ɵɵelementStart(0, "div", 7)(1, "span", 8);
8
+ i0.ɵɵtext(2, "Render time trace");
7
9
  i0.ɵɵelementEnd();
8
- i0.ɵɵelementStart(5, "span", 9);
9
- i0.ɵɵtext(6, "avg ");
10
- i0.ɵɵelementStart(7, "span", 10);
11
- i0.ɵɵtext(8);
10
+ i0.ɵɵelement(3, "perf-sparkline", 9);
12
11
  i0.ɵɵelementEnd();
13
- i0.ɵɵtext(9, " \u00B7 max ");
14
- i0.ɵɵelementStart(10, "span", 10);
15
- i0.ɵɵtext(11);
16
- i0.ɵɵelementEnd()()();
17
- i0.ɵɵelementStart(12, "div", 11);
18
- i0.ɵɵelement(13, "perf-sparkline", 12);
19
- i0.ɵɵelementEnd()()();
20
12
  } if (rf & 2) {
21
13
  const svc_r1 = i0.ɵɵnextContext(2);
22
14
  const ctx_r1 = i0.ɵɵnextContext();
23
- i0.ɵɵadvance(4);
24
- i0.ɵɵtextInterpolate1("Render Time Trace \u2014 Last ", svc_r1.sampleCount(), " Samples");
25
- i0.ɵɵadvance(4);
26
- i0.ɵɵtextInterpolate1("", ctx_r1.formatMs(svc_r1.avgRerender()), " ms");
15
+ i0.ɵɵproperty("title", "Last " + svc_r1.sampleCount() + " samples \u00B7 avg " + ctx_r1.formatMs(svc_r1.avgRerender()) + " ms \u00B7 max " + ctx_r1.formatMs(svc_r1.maxRerender()) + " ms \u00B7 line at " + ctx_r1.warnMs() + " ms");
27
16
  i0.ɵɵadvance(3);
28
- i0.ɵɵtextInterpolate1("", ctx_r1.formatMs(svc_r1.maxRerender()), " ms");
29
- i0.ɵɵadvance(2);
30
17
  i0.ɵɵproperty("samples", svc_r1.rerenders())("thresholdWarn", ctx_r1.warnMs())("height", 80);
31
18
  } }
32
19
  function RenderSectionComponent_Conditional_0_Conditional_0_Template(rf, ctx) { if (rf & 1) {
33
- i0.ɵɵelementStart(0, "div", 1)(1, "div", 2)(2, "div", 3)(3, "span", 4);
34
- i0.ɵɵtext(4, "Initial Render");
35
- i0.ɵɵelementEnd()();
36
- i0.ɵɵelementStart(5, "div", 5);
37
- i0.ɵɵtext(6);
38
- i0.ɵɵelementStart(7, "span");
39
- i0.ɵɵtext(8, "ms");
40
- i0.ɵɵelementEnd()()();
41
- i0.ɵɵelementStart(9, "div", 2)(10, "div", 3)(11, "span", 4);
42
- i0.ɵɵtext(12, "Avg Re-Render");
43
- i0.ɵɵelementEnd()();
44
- i0.ɵɵelementStart(13, "div", 5);
45
- i0.ɵɵtext(14);
46
- i0.ɵɵelementStart(15, "span");
47
- i0.ɵɵtext(16, "ms");
48
- i0.ɵɵelementEnd()();
49
- i0.ɵɵelementStart(17, "div", 6);
50
- i0.ɵɵtext(18);
51
- i0.ɵɵelementEnd()();
52
- i0.ɵɵelementStart(19, "div", 2)(20, "div", 3)(21, "span", 4);
53
- i0.ɵɵtext(22, "Slowest");
54
- i0.ɵɵelementEnd()();
55
- i0.ɵɵelementStart(23, "div", 5);
56
- i0.ɵɵtext(24);
57
- i0.ɵɵelementStart(25, "span");
58
- i0.ɵɵtext(26, "ms");
59
- i0.ɵɵelementEnd()()();
60
- i0.ɵɵelementStart(27, "div", 2)(28, "div", 3)(29, "span", 4);
61
- i0.ɵɵtext(30, "P95");
62
- i0.ɵɵelementEnd()();
63
- i0.ɵɵelementStart(31, "div", 5);
64
- i0.ɵɵtext(32);
65
- i0.ɵɵelementStart(33, "span");
66
- i0.ɵɵtext(34, "ms");
67
- i0.ɵɵelementEnd()()();
68
- i0.ɵɵelementStart(35, "div", 2)(36, "div", 3)(37, "span", 4);
69
- i0.ɵɵtext(38, "Last Render");
70
- i0.ɵɵelementEnd()();
71
- i0.ɵɵelementStart(39, "div", 5);
72
- i0.ɵɵtext(40);
73
- i0.ɵɵelementStart(41, "span");
74
- i0.ɵɵtext(42, "ms");
75
- i0.ɵɵelementEnd()()()();
76
- i0.ɵɵconditionalCreate(43, RenderSectionComponent_Conditional_0_Conditional_0_Conditional_43_Template, 14, 6, "div", 7);
20
+ i0.ɵɵelementStart(0, "div", 1);
21
+ i0.ɵɵelement(1, "prism-metric-card", 2)(2, "prism-metric-card", 3)(3, "prism-metric-card", 4)(4, "prism-metric-card", 5)(5, "prism-metric-card", 6);
22
+ i0.ɵɵelementEnd();
23
+ i0.ɵɵconditionalCreate(6, RenderSectionComponent_Conditional_0_Conditional_0_Conditional_6_Template, 4, 4, "div", 7);
77
24
  } if (rf & 2) {
78
25
  const svc_r1 = i0.ɵɵnextContext();
79
26
  const ctx_r1 = i0.ɵɵnextContext();
80
- i0.ɵɵadvance(5);
81
- i0.ɵɵstyleProp("color", ctx_r1.colorFor(svc_r1.initialRender()));
27
+ const initial_r3 = ctx_r1.statusOf(svc_r1.initialRender());
82
28
  i0.ɵɵadvance();
83
- i0.ɵɵtextInterpolate(ctx_r1.formatMs(svc_r1.initialRender()));
84
- i0.ɵɵadvance(8);
85
- i0.ɵɵtextInterpolate(ctx_r1.formatMs(svc_r1.avgRerender()));
86
- i0.ɵɵadvance(4);
87
- i0.ɵɵtextInterpolate1("", svc_r1.sampleCount(), " samples");
88
- i0.ɵɵadvance(6);
89
- i0.ɵɵtextInterpolate(ctx_r1.formatMs(svc_r1.maxRerender()));
90
- i0.ɵɵadvance(8);
91
- i0.ɵɵtextInterpolate(ctx_r1.formatMs(svc_r1.p95Rerender()));
92
- i0.ɵɵadvance(8);
93
- i0.ɵɵtextInterpolate(ctx_r1.formatMs(svc_r1.lastRerender()));
94
- i0.ɵɵadvance(3);
95
- i0.ɵɵconditional(svc_r1.rerenders().length > 1 ? 43 : -1);
29
+ i0.ɵɵproperty("value", ctx_r1.formatMs(svc_r1.initialRender()))("tone", initial_r3?.tone ?? null)("statusLabel", initial_r3?.label ?? null)("title", ctx_r1.budgetTitle("First render of the component"));
30
+ i0.ɵɵadvance();
31
+ i0.ɵɵproperty("value", ctx_r1.formatMs(svc_r1.avgRerender()))("title", svc_r1.sampleCount() + " samples");
32
+ i0.ɵɵadvance();
33
+ i0.ɵɵproperty("value", ctx_r1.formatMs(svc_r1.maxRerender()));
34
+ i0.ɵɵadvance();
35
+ i0.ɵɵproperty("value", ctx_r1.formatMs(svc_r1.p95Rerender()));
36
+ i0.ɵɵadvance();
37
+ i0.ɵɵproperty("value", ctx_r1.formatMs(svc_r1.lastRerender()));
38
+ i0.ɵɵadvance();
39
+ i0.ɵɵconditional(svc_r1.rerenders().length > 1 ? 6 : -1);
96
40
  } }
97
41
  function RenderSectionComponent_Conditional_0_Conditional_1_Template(rf, ctx) { if (rf & 1) {
98
- i0.ɵɵelementStart(0, "div", 0)(1, "div", 13);
42
+ i0.ɵɵelementStart(0, "div", 0)(1, "span");
99
43
  i0.ɵɵtext(2, "No profiling data yet");
100
44
  i0.ɵɵelementEnd();
101
- i0.ɵɵelementStart(3, "div", 14);
45
+ i0.ɵɵelementStart(3, "span");
102
46
  i0.ɵɵtext(4, "Click Profile to start recording render times");
103
47
  i0.ɵɵelementEnd()();
104
48
  } }
105
49
  function RenderSectionComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
106
- i0.ɵɵconditionalCreate(0, RenderSectionComponent_Conditional_0_Conditional_0_Template, 44, 9)(1, RenderSectionComponent_Conditional_0_Conditional_1_Template, 5, 0, "div", 0);
50
+ i0.ɵɵconditionalCreate(0, RenderSectionComponent_Conditional_0_Conditional_0_Template, 7, 10)(1, RenderSectionComponent_Conditional_0_Conditional_1_Template, 5, 0, "div", 0);
107
51
  } if (rf & 2) {
108
52
  const svc_r1 = ctx;
109
53
  i0.ɵɵconditional(svc_r1.initialRender() !== null || svc_r1.rerenders().length > 0 ? 0 : 1);
@@ -115,41 +59,31 @@ export class RenderSectionComponent {
115
59
  ...(ngDevMode ? [{ debugName: "warnMs" }] : /* istanbul ignore next */ []));
116
60
  critMs = input(16, /* @ts-ignore */
117
61
  ...(ngDevMode ? [{ debugName: "critMs" }] : /* istanbul ignore next */ []));
118
- initialClass = computed(() => {
119
- const v = this.renderService().initialRender();
120
- if (v === null)
121
- return '';
122
- if (v >= this.critMs())
123
- return 'crit';
124
- if (v >= this.warnMs())
125
- return 'warn';
126
- return '';
127
- }, /* @ts-ignore */
128
- ...(ngDevMode ? [{ debugName: "initialClass" }] : /* istanbul ignore next */ []));
129
62
  formatMs(value) {
130
63
  if (value === null)
131
64
  return '—';
132
65
  return value < 10 ? value.toFixed(2) : value.toFixed(1);
133
66
  }
134
- colorFor(value) {
67
+ /** A measured render time graded against the budget; `null` when there is nothing to grade. */
68
+ statusOf(value) {
135
69
  if (value === null)
136
- return '';
137
- if (value >= this.critMs())
138
- return 'var(--prism-danger)';
139
- if (value >= this.warnMs())
140
- return 'var(--prism-warn)';
141
- return 'var(--prism-success)';
70
+ return null;
71
+ const status = perfStatus(value, this.warnMs(), this.critMs());
72
+ return { tone: toneForStatus(status.tone), label: status.label };
73
+ }
74
+ budgetTitle(what) {
75
+ return `${what} · warning above ${this.warnMs()} ms, critical above ${this.critMs()} ms`;
142
76
  }
143
77
  static ɵfac = function RenderSectionComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RenderSectionComponent)(); };
144
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RenderSectionComponent, selectors: [["perf-render-section"]], inputs: { renderService: [1, "renderService"], warnMs: [1, "warnMs"], critMs: [1, "critMs"] }, decls: 1, vars: 1, consts: [[1, "perf-empty"], [1, "perf-body"], [1, "perf-card"], [1, "perf-card-head"], [1, "perf-card-name"], [1, "perf-card-val"], [1, "perf-card-sub"], [1, "perf-detail"], [1, "perf-card", "perf-card--block"], [1, "perf-sparkline-legend"], [1, "perf-sparkline-hl"], [1, "perf-sparkline-wrap"], [3, "samples", "thresholdWarn", "height"], [1, "perf-empty-text"], [1, "perf-empty-hint"]], template: function RenderSectionComponent_Template(rf, ctx) { if (rf & 1) {
78
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RenderSectionComponent, selectors: [["perf-render-section"]], inputs: { renderService: [1, "renderService"], warnMs: [1, "warnMs"], critMs: [1, "critMs"] }, decls: 1, vars: 1, consts: [[1, "perf-empty"], [1, "perf-grid"], ["label", "Initial render", "unit", "ms", 3, "value", "tone", "statusLabel", "title"], ["label", "Avg re-render", "unit", "ms", 3, "value", "title"], ["label", "Slowest", "unit", "ms", 3, "value"], ["label", "P95", "unit", "ms", 3, "value"], ["label", "Last render", "unit", "ms", 3, "value"], [1, "perf-block", 3, "title"], [1, "perf-block__label"], [3, "samples", "thresholdWarn", "height"]], template: function RenderSectionComponent_Template(rf, ctx) { if (rf & 1) {
145
79
  i0.ɵɵconditionalCreate(0, RenderSectionComponent_Conditional_0_Template, 2, 1);
146
80
  } if (rf & 2) {
147
81
  let tmp_0_0;
148
82
  i0.ɵɵconditional((tmp_0_0 = ctx.renderService()) ? 0 : -1, tmp_0_0);
149
- } }, dependencies: [SparklineComponent], styles: [".perf-body[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 16px;\n align-content: start;\n padding: 20px 24px;\n}\n\n.perf-card[_ngcontent-%COMP%] {\n padding: 16px;\n background: var(--%NS%prism-bg-surface);\n border: 1px solid var(--%NS%prism-border);\n border-radius: 10px;\n}\n\n.perf-card--block[_ngcontent-%COMP%] {\n display: block;\n}\n\n.perf-card-head[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 10px;\n}\n\n.perf-card-name[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 700;\n color: var(--%NS%prism-text-ghost);\n text-transform: uppercase;\n letter-spacing: 0.08em;\n}\n\n.perf-card-val[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n align-items: baseline;\n font-family: 'JetBrains Mono', monospace;\n font-size: 26px;\n font-weight: 700;\n color: var(--%NS%prism-text);\n letter-spacing: -0.02em;\n}\n\n.perf-card-val[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: 500;\n color: var(--%NS%prism-text-muted);\n}\n\n.perf-card-sub[_ngcontent-%COMP%] {\n margin-top: 4px;\n font-family: 'JetBrains Mono', monospace;\n font-size: 11px;\n color: var(--%NS%prism-text-muted);\n}\n\n.perf-detail[_ngcontent-%COMP%] {\n padding: 0 24px 20px;\n}\n\n.perf-sparkline-legend[_ngcontent-%COMP%] {\n font-family: 'JetBrains Mono', monospace;\n font-size: 11px;\n color: var(--%NS%prism-text-muted);\n}\n\n.perf-sparkline-hl[_ngcontent-%COMP%] {\n color: var(--%NS%prism-primary);\n}\n\n.perf-sparkline-wrap[_ngcontent-%COMP%] {\n position: relative;\n margin-top: 10px;\n}\n\n.perf-empty[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 180px;\n}\n\n.perf-empty-text[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--%NS%prism-text-ghost);\n}\n\n.perf-empty-hint[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--%NS%prism-text-ghost);\n opacity: 0.6;\n}"] });
83
+ } }, dependencies: [SparklineComponent, PrismMetricCardComponent], styles: ["\n\n\n[_nghost-%COMP%] {\n display: block;\n}\n\n.perf-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n gap: 12px;\n padding: 16px 18px 0;\n}\n\n.perf-block[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px 12px;\n margin: 12px 18px 0;\n background: var(--%NS%prism-bg-surface);\n border: 1px solid var(--%NS%prism-border);\n border-radius: 6px;\n}\n\n.perf-block[_ngcontent-%COMP%]:last-child {\n margin-bottom: 16px;\n}\n\n.perf-block__label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n color: var(--%NS%prism-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n\n.perf-bars[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 84px minmax(0, 1fr) auto;\n gap: 10px;\n align-items: center;\n font-family: var(--%NS%font-mono);\n font-size: 12px;\n}\n\n.perf-bar-label[_ngcontent-%COMP%] {\n color: var(--%NS%prism-text-muted);\n}\n\n.perf-bar-track[_ngcontent-%COMP%] {\n height: 6px;\n overflow: hidden;\n background: var(--%NS%prism-input-bg);\n border-radius: 3px;\n}\n\n.perf-bar-fill[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--%NS%prism-primary);\n border-radius: 3px;\n}\n\n.perf-bar-val[_ngcontent-%COMP%] {\n color: var(--%NS%prism-text-2);\n white-space: nowrap;\n}\n\n.perf-empty[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 160px;\n font-size: 12.5px;\n color: var(--%NS%prism-text-muted);\n}\n\n.perf-block[_ngcontent-%COMP%] > prism-badge[_ngcontent-%COMP%] {\n align-self: flex-start;\n}"] });
150
84
  }
151
85
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RenderSectionComponent, [{
152
86
  type: Component,
153
- args: [{ selector: 'perf-render-section', standalone: true, imports: [SparklineComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (renderService(); as svc) {\n @if (svc.initialRender() !== null || svc.rerenders().length > 0) {\n <div class=\"perf-body\">\n <div class=\"perf-card\">\n <div class=\"perf-card-head\"><span class=\"perf-card-name\">Initial Render</span></div>\n <div\n class=\"perf-card-val\"\n [style.color]=\"colorFor(svc.initialRender())\"\n >{{ formatMs(svc.initialRender()) }}<span>ms</span></div\n >\n </div>\n <div class=\"perf-card\">\n <div class=\"perf-card-head\"><span class=\"perf-card-name\">Avg Re-Render</span></div>\n <div class=\"perf-card-val\">{{ formatMs(svc.avgRerender()) }}<span>ms</span></div>\n <div class=\"perf-card-sub\">{{ svc.sampleCount() }} samples</div>\n </div>\n <div class=\"perf-card\">\n <div class=\"perf-card-head\"><span class=\"perf-card-name\">Slowest</span></div>\n <div class=\"perf-card-val\">{{ formatMs(svc.maxRerender()) }}<span>ms</span></div>\n </div>\n <div class=\"perf-card\">\n <div class=\"perf-card-head\"><span class=\"perf-card-name\">P95</span></div>\n <div class=\"perf-card-val\">{{ formatMs(svc.p95Rerender()) }}<span>ms</span></div>\n </div>\n <div class=\"perf-card\">\n <div class=\"perf-card-head\"><span class=\"perf-card-name\">Last Render</span></div>\n <div class=\"perf-card-val\">{{ formatMs(svc.lastRerender()) }}<span>ms</span></div>\n </div>\n </div>\n\n @if (svc.rerenders().length > 1) {\n <div class=\"perf-detail\">\n <div class=\"perf-card perf-card--block\">\n <div class=\"perf-card-head\">\n <span class=\"perf-card-name\">Render Time Trace &mdash; Last {{ svc.sampleCount() }} Samples</span>\n <span class=\"perf-sparkline-legend\"\n >avg <span class=\"perf-sparkline-hl\">{{ formatMs(svc.avgRerender()) }} ms</span> &middot; max\n <span class=\"perf-sparkline-hl\">{{ formatMs(svc.maxRerender()) }} ms</span></span\n >\n </div>\n <div class=\"perf-sparkline-wrap\">\n <perf-sparkline\n [samples]=\"svc.rerenders()\"\n [thresholdWarn]=\"warnMs()\"\n [height]=\"80\" />\n </div>\n </div>\n </div>\n }\n } @else {\n <div class=\"perf-empty\">\n <div class=\"perf-empty-text\">No profiling data yet</div>\n <div class=\"perf-empty-hint\">Click Profile to start recording render times</div>\n </div>\n }\n}\n", styles: [".perf-body {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 16px;\n align-content: start;\n padding: 20px 24px;\n}\n\n.perf-card {\n padding: 16px;\n background: var(--prism-bg-surface);\n border: 1px solid var(--prism-border);\n border-radius: 10px;\n}\n\n.perf-card--block {\n display: block;\n}\n\n.perf-card-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 10px;\n}\n\n.perf-card-name {\n font-size: 11px;\n font-weight: 700;\n color: var(--prism-text-ghost);\n text-transform: uppercase;\n letter-spacing: 0.08em;\n}\n\n.perf-card-val {\n display: flex;\n gap: 4px;\n align-items: baseline;\n font-family: 'JetBrains Mono', monospace;\n font-size: 26px;\n font-weight: 700;\n color: var(--prism-text);\n letter-spacing: -0.02em;\n}\n\n.perf-card-val span {\n font-size: 13px;\n font-weight: 500;\n color: var(--prism-text-muted);\n}\n\n.perf-card-sub {\n margin-top: 4px;\n font-family: 'JetBrains Mono', monospace;\n font-size: 11px;\n color: var(--prism-text-muted);\n}\n\n.perf-detail {\n padding: 0 24px 20px;\n}\n\n.perf-sparkline-legend {\n font-family: 'JetBrains Mono', monospace;\n font-size: 11px;\n color: var(--prism-text-muted);\n}\n\n.perf-sparkline-hl {\n color: var(--prism-primary);\n}\n\n.perf-sparkline-wrap {\n position: relative;\n margin-top: 10px;\n}\n\n.perf-empty {\n display: flex;\n flex-direction: column;\n gap: 8px;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 180px;\n}\n\n.perf-empty-text {\n font-size: 12px;\n color: var(--prism-text-ghost);\n}\n\n.perf-empty-hint {\n font-size: 11px;\n color: var(--prism-text-ghost);\n opacity: 0.6;\n}\n"] }]
87
+ args: [{ selector: 'perf-render-section', standalone: true, imports: [SparklineComponent, PrismMetricCardComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (renderService(); as svc) {\n @if (svc.initialRender() !== null || svc.rerenders().length > 0) {\n <div class=\"perf-grid\">\n @let initial = statusOf(svc.initialRender());\n <prism-metric-card\n label=\"Initial render\"\n unit=\"ms\"\n [value]=\"formatMs(svc.initialRender())\"\n [tone]=\"initial?.tone ?? null\"\n [statusLabel]=\"initial?.label ?? null\"\n [title]=\"budgetTitle('First render of the component')\" />\n <prism-metric-card\n label=\"Avg re-render\"\n unit=\"ms\"\n [value]=\"formatMs(svc.avgRerender())\"\n [title]=\"svc.sampleCount() + ' samples'\" />\n <prism-metric-card\n label=\"Slowest\"\n unit=\"ms\"\n [value]=\"formatMs(svc.maxRerender())\" />\n <prism-metric-card\n label=\"P95\"\n unit=\"ms\"\n [value]=\"formatMs(svc.p95Rerender())\" />\n <prism-metric-card\n label=\"Last render\"\n unit=\"ms\"\n [value]=\"formatMs(svc.lastRerender())\" />\n </div>\n\n @if (svc.rerenders().length > 1) {\n <div\n class=\"perf-block\"\n [title]=\"\n 'Last ' +\n svc.sampleCount() +\n ' samples \u00B7 avg ' +\n formatMs(svc.avgRerender()) +\n ' ms \u00B7 max ' +\n formatMs(svc.maxRerender()) +\n ' ms \u00B7 line at ' +\n warnMs() +\n ' ms'\n \">\n <span class=\"perf-block__label\">Render time trace</span>\n <perf-sparkline\n [samples]=\"svc.rerenders()\"\n [thresholdWarn]=\"warnMs()\"\n [height]=\"80\" />\n </div>\n }\n } @else {\n <div class=\"perf-empty\">\n <span>No profiling data yet</span>\n <span>Click Profile to start recording render times</span>\n </div>\n }\n}\n", styles: ["/* Shared by the bundle, render and memory sections: metric cards on a 12 px\n grid, block cards for bars and traces, and the empty state. The cards\n themselves are prism-metric-card from @ng-prism/core. */\n:host {\n display: block;\n}\n\n.perf-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n gap: 12px;\n padding: 16px 18px 0;\n}\n\n.perf-block {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px 12px;\n margin: 12px 18px 0;\n background: var(--prism-bg-surface);\n border: 1px solid var(--prism-border);\n border-radius: 6px;\n}\n\n.perf-block:last-child {\n margin-bottom: 16px;\n}\n\n.perf-block__label {\n font-size: 11px;\n font-weight: 600;\n color: var(--prism-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n/* Quantities, not verdicts: one colour, the primary, never the brand gradient. */\n.perf-bars {\n display: grid;\n grid-template-columns: 84px minmax(0, 1fr) auto;\n gap: 10px;\n align-items: center;\n font-family: var(--font-mono);\n font-size: 12px;\n}\n\n.perf-bar-label {\n color: var(--prism-text-muted);\n}\n\n.perf-bar-track {\n height: 6px;\n overflow: hidden;\n background: var(--prism-input-bg);\n border-radius: 3px;\n}\n\n.perf-bar-fill {\n height: 100%;\n background: var(--prism-primary);\n border-radius: 3px;\n}\n\n.perf-bar-val {\n color: var(--prism-text-2);\n white-space: nowrap;\n}\n\n.perf-empty {\n display: flex;\n flex-direction: column;\n gap: 6px;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 160px;\n font-size: 12.5px;\n color: var(--prism-text-muted);\n}\n\n.perf-block > prism-badge {\n align-self: flex-start;\n}\n"] }]
154
88
  }], null, { renderService: [{ type: i0.Input, args: [{ isSignal: true, alias: "renderService", required: true }] }], warnMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "warnMs", required: false }] }], critMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "critMs", required: false }] }] }); })();
155
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RenderSectionComponent, { className: "RenderSectionComponent", filePath: "render/render-section.component.ts", lineNumber: 13 }); })();
89
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RenderSectionComponent, { className: "RenderSectionComponent", filePath: "render/render-section.component.ts", lineNumber: 15 }); })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-prism/plugin-perf",
3
- "version": "22.6.0",
3
+ "version": "22.7.0-beta.1",
4
4
  "description": "Performance profiling panel for ng-prism: bundle size, render time, memory footprint.",
5
5
  "ng-add": {
6
6
  "save": "devDependencies"
@@ -36,7 +36,7 @@
36
36
  "peerDependencies": {
37
37
  "@angular/core": ">=20.0.0",
38
38
  "typescript": ">=5.0.0",
39
- "@ng-prism/core": "^22.6.0-0",
39
+ "@ng-prism/core": "^22.7.0-0",
40
40
  "@angular-devkit/schematics": ">=20.0.0"
41
41
  },
42
42
  "devDependencies": {