@hestia-earth/ui-components 0.42.27 → 0.42.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, InjectionToken, inject, ElementRef, output, Directive, DestroyRef, input, computed, HostBinding, Component as Component$1, ChangeDetectionStrategy, Pipe, viewChild, HostListener, model, ViewEncapsulation, signal, effect, contentChild, untracked, viewChildren, forwardRef, LOCALE_ID } from '@angular/core';
2
+ import { Injectable, InjectionToken, inject, ElementRef, PLATFORM_ID, output, Directive, DestroyRef, input, computed, HostBinding, Component as Component$1, ChangeDetectionStrategy, Pipe, viewChild, HostListener, model, ViewEncapsulation, signal, effect, contentChild, untracked, viewChildren, forwardRef, LOCALE_ID } from '@angular/core';
3
3
  import { toObservable, toSignal, outputFromObservable, takeUntilDestroyed, rxResource } from '@angular/core/rxjs-interop';
4
4
  import * as i1 from '@angular/forms';
5
5
  import { FormsModule, NG_VALUE_ACCESSOR, UntypedFormBuilder, Validators, ReactiveFormsModule, FormControl } from '@angular/forms';
6
- import { NgClass, DecimalPipe, NgTemplateOutlet, KeyValuePipe, DOCUMENT, PlatformLocation, NgStyle, UpperCasePipe, JsonPipe, DatePipe, AsyncPipe, formatDate as formatDate$1 } from '@angular/common';
6
+ import { isPlatformBrowser, NgClass, DecimalPipe, NgTemplateOutlet, DOCUMENT, KeyValuePipe, PlatformLocation, NgStyle, UpperCasePipe, JsonPipe, DatePipe, AsyncPipe, formatDate as formatDate$1 } from '@angular/common';
7
7
  import * as i1$1 from '@ng-bootstrap/ng-bootstrap';
8
8
  import { NgbTooltip, NgbDropdown, NgbDropdownMenu, NgbDropdownToggle, NgbActiveModal, NgbHighlight, NgbModal, NgbDropdownItem, NgbTypeahead, NgbPopover, NgbTooltipModule, NgbDropdownModule, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap';
9
- import { ReplaySubject, of, mergeMap, shareReplay, delay, map, catchError, distinctUntilChanged, timer, take, first, Subject, combineLatest, filter, fromEvent, startWith, merge, skip, switchMap, EMPTY, throttleTime, animationFrameScheduler, debounceTime, takeUntil, lastValueFrom, tap, skipUntil, zip, mergeAll, reduce, forkJoin, throwError, from, firstValueFrom, toArray, distinct, groupBy } from 'rxjs';
9
+ import { ReplaySubject, of, mergeMap, shareReplay, delay, map, catchError, distinctUntilChanged, timer, take, first, EMPTY, Subject, combineLatest, filter, fromEvent, startWith, merge, skip, switchMap, throttleTime, animationFrameScheduler, debounceTime, takeUntil, lastValueFrom, tap, skipUntil, zip, mergeAll, reduce, forkJoin, throwError, from, firstValueFrom, toArray, distinct, groupBy } from 'rxjs';
10
10
  import { HttpClient } from '@angular/common/http';
11
11
  import get from 'lodash.get';
12
12
  import { SCHEMA_VERSION, SchemaType, NodeType, TermTermType, productTermTermType, nestedSearchableKeys, SiteSiteType, EmissionMethodTier, isExpandable, sortKeysByType, isTypeNode, uniquenessFields, BlankNodesKey, impactAssessmentTermTermType, measurementTermTermType, emissionTermTermType, inputTermTermType, CycleFunctionalUnit, NonBlankNodesKey, jsonldPath, isTypeValid, typeToSchemaType, infrastructureTermTermType, managementTermTermType } from '@hestia-earth/schema';
@@ -76,16 +76,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
76
76
 
77
77
  const gitHome = 'https://gitlab.com/hestia-earth';
78
78
  const gitBranch = () => (['dev', 'staging'].some(env => baseUrl().includes(env)) ? 'develop' : 'master');
79
- const isChrome = () => window.navigator.userAgent.includes('Chrome');
80
- const baseUrl = (allowLocalhost = true) => window.location.origin?.includes('localhost')
79
+ /**
80
+ * Access `window` defensively so this module can be evaluated outside the browser
81
+ * (SSR / prerender / Node). On the server there is no location, so callers fall
82
+ * back to production defaults.
83
+ */
84
+ const windowRef = () => (typeof window === 'undefined' ? undefined : window);
85
+ const origin = () => windowRef()?.location?.origin ?? '';
86
+ const isChrome = () => windowRef()?.navigator?.userAgent?.includes('Chrome') ?? false;
87
+ const baseUrl = (allowLocalhost = true) => origin().includes('localhost')
81
88
  ? allowLocalhost
82
- ? window.location.origin
89
+ ? origin()
83
90
  : 'https://www-dev.hestia.earth'
84
- : window.location.origin?.includes('hestia.earth')
85
- ? window.location.origin
91
+ : origin().includes('hestia.earth')
92
+ ? origin()
86
93
  : 'https://www.hestia.earth';
87
94
  const baseApiUrl = () => baseUrl(false).replace('www', 'api');
88
- const isExternal = () => baseUrl() !== window.location.origin;
95
+ const isExternal = () => baseUrl() !== origin();
89
96
  const schemaBaseUrl = (version) => [baseUrl(), 'schema', version].filter(Boolean).join('/');
90
97
  const schemaDataBaseUrl = (version) => [baseUrl(false), 'schema-data', version || SCHEMA_VERSION].filter(Boolean).join('/');
91
98
  const glossaryBaseUrl = (allowLocalhost = true) => [baseUrl(allowLocalhost), 'glossary'].filter(Boolean).join('/');
@@ -120,7 +127,11 @@ const filterParams = (obj) => {
120
127
  });
121
128
  return res;
122
129
  };
123
- const waitFor = (variable, callback) => get(window, variable, false) ? callback() : setTimeout(() => waitFor(variable, callback), 100);
130
+ const waitFor = (variable, callback) => typeof window === 'undefined'
131
+ ? undefined
132
+ : get(window, variable, false)
133
+ ? callback()
134
+ : setTimeout(() => waitFor(variable, callback), 100);
124
135
  const bottom = (element) => element.offsetTop + element.getBoundingClientRect().height;
125
136
  const isScrolledBelow = (element) => (element ? window.scrollY > bottom(element) : false);
126
137
  const scrollToEl = (id, args = {
@@ -1545,14 +1556,18 @@ class ResizedEvent {
1545
1556
  class ResizedDirective {
1546
1557
  constructor() {
1547
1558
  this.element = inject(ElementRef);
1559
+ this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
1548
1560
  this.resized = output();
1549
- this.observer = new ResizeObserver(entries => this.observe(entries));
1561
+ // `ResizeObserver` is browser-only; there are no resize events during SSR.
1562
+ if (this.isBrowser) {
1563
+ this.observer = new ResizeObserver(entries => this.observe(entries));
1564
+ }
1550
1565
  }
1551
1566
  ngOnInit() {
1552
- this.observer.observe(this.element.nativeElement);
1567
+ this.observer?.observe(this.element.nativeElement);
1553
1568
  }
1554
1569
  ngOnDestroy() {
1555
- this.observer.disconnect();
1570
+ this.observer?.disconnect();
1556
1571
  }
1557
1572
  observe(entries) {
1558
1573
  const domSize = entries[0];
@@ -1574,6 +1589,10 @@ const distinctUntilChangedDeep = () => distinctUntilChanged((x, y) => isEqual$1(
1574
1589
  const takeAfterViewInit = (fn, settings = { take: 10 }) => timer(0, 50).pipe(take(settings.take), map(() => fn()), first(returnItem => !!returnItem));
1575
1590
  const injectResizeEvent$ = (elementRef) => {
1576
1591
  const destroyRef = inject(DestroyRef);
1592
+ // `ResizeObserver` is browser-only; there are no resize events during SSR.
1593
+ if (!isPlatformBrowser(inject(PLATFORM_ID))) {
1594
+ return EMPTY;
1595
+ }
1577
1596
  const resizedEvent$ = new Subject();
1578
1597
  let oldRect;
1579
1598
  const observer = new ResizeObserver(entries => {
@@ -1723,7 +1742,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
1723
1742
 
1724
1743
  const GUIDE_ENABLED = new InjectionToken('HESTIA_GUIDE_ENABLED');
1725
1744
  const guideNamespace = 'he-guide';
1726
- const postGuideEvent = (data) => window.parent.postMessage({ namespace: guideNamespace, ...data }, '*');
1745
+ const postGuideEvent = (data) => typeof window === 'undefined'
1746
+ ? undefined
1747
+ : window.parent.postMessage({ namespace: guideNamespace, ...data }, '*');
1727
1748
  const handleGuideEvent = (handler) => (event) => {
1728
1749
  const data = event.data;
1729
1750
  if (data.namespace == guideNamespace) {
@@ -2029,7 +2050,10 @@ const toBreakpoint = ({ min, max }) => [min ? toSize(beakpointWidths[min], 'min'
2029
2050
  class ResponsiveService {
2030
2051
  constructor() {
2031
2052
  this.breakPointObserver = inject(BreakpointObserver);
2032
- this.windowWidth$ = fromEvent(window, 'resize').pipe(startWith(window.innerWidth), map(() => window.innerWidth));
2053
+ this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
2054
+ this.windowWidth$ = this.isBrowser
2055
+ ? fromEvent(window, 'resize').pipe(startWith(window.innerWidth), map(() => window.innerWidth))
2056
+ : of(0);
2033
2057
  this.isMobile$ = this.breakPointObserver.observe(toBreakpoint({ max: Breakpoint.tablet })).pipe(map(state => state.matches), distinctUntilChanged());
2034
2058
  this.isTablet$ = this.breakPointObserver
2035
2059
  .observe(toBreakpoint({ min: Breakpoint.tablet, max: Breakpoint.desktop }))
@@ -2047,7 +2071,7 @@ class ResponsiveService {
2047
2071
  */
2048
2072
  this.is1080p$ = this.breakPointObserver.observe(toSize(1920, 'min')).pipe(map(state => state.matches), distinctUntilChanged());
2049
2073
  this.isRetinaDisplay = () => {
2050
- if (window.matchMedia) {
2074
+ if (this.isBrowser && window.matchMedia) {
2051
2075
  const mq = window.matchMedia([
2052
2076
  'min--moz-device-pixel-ratio: 1.3',
2053
2077
  '-o-min-device-pixel-ratio: 2.6/2',
@@ -2117,6 +2141,8 @@ class DrawerContainerComponent {
2117
2141
  constructor() {
2118
2142
  this.responsiveService = inject(ResponsiveService);
2119
2143
  this.localStorage = inject(LocalStorageService);
2144
+ this.document = inject(DOCUMENT);
2145
+ this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
2120
2146
  this.contentContainer = viewChild('contentContainer', { ...(ngDevMode ? { debugName: "contentContainer" } : {}), read: ElementRef });
2121
2147
  this.menuState = signal('close', ...(ngDevMode ? [{ debugName: "menuState" }] : []));
2122
2148
  /**
@@ -2199,7 +2225,9 @@ class DrawerContainerComponent {
2199
2225
  this.destroy$ = new Subject();
2200
2226
  this._updateSidenavEvent$ = toObservable(this.isResizing).pipe(switchMap(listen => (listen ? moveEvent() : EMPTY)), throttleTime(0, animationFrameScheduler));
2201
2227
  this._contentContainer$ = toObservable(this.contentContainer).pipe(filter(v => !!v));
2202
- this._hostComponentWidth$ = fromEvent(window, 'resize').pipe(startWith(window.innerWidth));
2228
+ this._hostComponentWidth$ = this.isBrowser
2229
+ ? fromEvent(window, 'resize').pipe(startWith(window.innerWidth))
2230
+ : of(0);
2203
2231
  /**
2204
2232
  * we use combineLatest instead of computed to avoid the animation flickering (wait for all sources to emit)
2205
2233
  */
@@ -2221,13 +2249,13 @@ class DrawerContainerComponent {
2221
2249
  effect(() => {
2222
2250
  const position = this.position();
2223
2251
  const width = this.sidenavWidth();
2224
- document.body.style.setProperty(`--sidenav-${position}-width`, `${width}px`);
2252
+ this.document.body.style.setProperty(`--sidenav-${position}-width`, `${width}px`);
2225
2253
  });
2226
2254
  effect(() => {
2227
2255
  const expanded = this.expanded();
2228
2256
  const position = this.position();
2229
2257
  const value = this.contentContainerTransform();
2230
- document.body.style.setProperty(`--content-transition-${position}-x`, `${expanded ? value : 0}px`);
2258
+ this.document.body.style.setProperty(`--content-transition-${position}-x`, `${expanded ? value : 0}px`);
2231
2259
  });
2232
2260
  // initial load of configuration
2233
2261
  effect(() => this.storageKey() && this.storeConfiguration());
@@ -2304,6 +2332,8 @@ class DrawerContainerComponent {
2304
2332
  this.menuState.set(didOpen ? 'open' : 'close');
2305
2333
  }
2306
2334
  _getContentLeftSideSpace(element) {
2335
+ if (!this.isBrowser)
2336
+ return 0;
2307
2337
  const position = this.position();
2308
2338
  const styles = window.getComputedStyle(element.nativeElement, null);
2309
2339
  const padding = styles.getPropertyValue('padding-' + position);
@@ -3006,6 +3036,12 @@ const loadSvgSprite = () => {
3006
3036
  const http = inject(HttpClient);
3007
3037
  const document = inject(DOCUMENT);
3008
3038
  const platformLocation = inject(PlatformLocation);
3039
+ // The sprite is injected into the DOM for the browser to render `<use>` refs.
3040
+ // During SSR/prerender there is no asset server to fetch it from, and the
3041
+ // client re-injects it on hydration, so skip the fetch on the server.
3042
+ if (!isPlatformBrowser(inject(PLATFORM_ID))) {
3043
+ return Promise.resolve();
3044
+ }
3009
3045
  const baseUrl = platformLocation.getBaseHrefFromDOM();
3010
3046
  return lastValueFrom(http.get(`${baseUrl}assets/svg-icons/icons-sprite.svg`, { responseType: 'text' }).pipe(tap(svgContent => {
3011
3047
  const div = document.createElement('div');
@@ -3464,10 +3500,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
3464
3500
  class SocialTagsComponent {
3465
3501
  constructor() {
3466
3502
  this.meta = inject(Meta);
3503
+ this.document = inject(DOCUMENT);
3467
3504
  this.config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : []));
3468
3505
  this.classes = 'is-hidden';
3469
3506
  this.configs = computed(() => ({
3470
- 'og:url': window.location.href.split('?')[0],
3507
+ 'og:url': this.document.location.href.split('?')[0],
3471
3508
  ...this.config
3472
3509
  }), ...(ngDevMode ? [{ debugName: "configs" }] : []));
3473
3510
  this.meta.addTag({ charset: 'UTF-8' });
@@ -3562,8 +3599,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
3562
3599
  /* eslint-disable @angular-eslint/directive-selector */
3563
3600
  class DocumentClickService {
3564
3601
  constructor() {
3602
+ this.document = inject(DOCUMENT);
3565
3603
  this.documentClick$ = new Subject();
3566
- fromEvent(document, 'click')
3604
+ // Document click events only exist in the browser; skip on the server (SSR).
3605
+ if (!isPlatformBrowser(inject(PLATFORM_ID))) {
3606
+ return;
3607
+ }
3608
+ fromEvent(this.document, 'click')
3567
3609
  .pipe(takeUntilDestroyed(), tap(event => this.documentClick$.next(event)))
3568
3610
  .subscribe();
3569
3611
  }
@@ -6796,6 +6838,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6796
6838
  }]
6797
6839
  }] });
6798
6840
 
6841
+ /**
6842
+ * Sum the values of every item of a group, skipping the items without a value at this index.
6843
+ *
6844
+ * An item has no value when the Node does not contain that Emission at all, which is not the same as a `0` value:
6845
+ * summing it as `NaN` would silently hide the whole group from the chart. When no item of the group has a value,
6846
+ * `undefined` is returned so the chart displays "No data", like it does when the data is not grouped.
6847
+ *
6848
+ * @param values The values of every item of the group, at a single index.
6849
+ * @returns The sum, or `undefined` when no item has a value.
6850
+ */
6851
+ const sumValues = (values) => {
6852
+ const definedValues = values.filter(value => !isUndefined(value));
6853
+ return definedValues.length ? sum(definedValues) : undefined;
6854
+ };
6855
+ const groupByCategory = (data, category, emissionCategoryValue) => data.reduce((prev, curr) => {
6856
+ const groupKey = emissionCategoryValue(curr.id, category);
6857
+ prev[groupKey] = prev[groupKey] || [];
6858
+ prev[groupKey].push(curr);
6859
+ return prev;
6860
+ }, {});
6861
+ /**
6862
+ * Group the contribution data by Category, summing the values of every Emission of the same Category.
6863
+ *
6864
+ * @param data The contribution data, one item per Emission.
6865
+ * @param labels The labels of the chart, one per Node compared.
6866
+ * @param category The Category to group by. When empty, the data is returned as-is.
6867
+ * @param service Used to resolve the Category of an Emission and its label.
6868
+ * @returns The data grouped by Category, or the original data when grouping is not needed.
6869
+ */
6870
+ const groupDataByCategory = (data, labels, category, { emissionCategoryValue, categoryLabel }) => {
6871
+ if (!category) {
6872
+ return data;
6873
+ }
6874
+ const groupedData = groupByCategory(data, category, emissionCategoryValue);
6875
+ return Object.keys(groupedData).length === 1
6876
+ ? data
6877
+ : Object.entries(groupedData).map(([groupKey, items], index) => ({
6878
+ label: categoryLabel(groupKey),
6879
+ values: labels.map((_, i) => sumValues(items.map(item => item.values[i]))),
6880
+ color: listColor(groupKey, index),
6881
+ includedItems: items.map((item, i) => ({
6882
+ ...item,
6883
+ color: listColor(item.id, i)
6884
+ }))
6885
+ }));
6886
+ };
6887
+
6799
6888
  const grey = '#4a4a4a';
6800
6889
  const barheight = 8; // 8px
6801
6890
  const hoverHeight = 10; // 10px
@@ -6986,30 +7075,7 @@ class ContributionChartComponent {
6986
7075
  this.exporting = computed(() => this.chart()?.exporting(), ...(ngDevMode ? [{ debugName: "exporting" }] : []));
6987
7076
  this.chartHeight = computed(() => this.height() || chartHeight$1(this.minHeight(), this.maxHeight(), this.labels()?.length ?? 0), ...(ngDevMode ? [{ debugName: "chartHeight" }] : []));
6988
7077
  this.hasNegativeContributions = computed(() => this.displayData()?.some(value => value.values.some(v => v < 0)), ...(ngDevMode ? [{ debugName: "hasNegativeContributions" }] : []));
6989
- this.displayData = computed(() => {
6990
- const category = this.category();
6991
- const data = this.data();
6992
- if (!category) {
6993
- return data;
6994
- }
6995
- const groupedData = data.reduce((prev, curr) => {
6996
- const groupKey = this.emissionCategoryService.emissionCategoryValue(curr.id, category);
6997
- prev[groupKey] = prev[groupKey] || [];
6998
- prev[groupKey].push(curr);
6999
- return prev;
7000
- }, {});
7001
- return Object.keys(groupedData).length === 1
7002
- ? data
7003
- : Object.entries(groupedData).map(([category, items], index) => ({
7004
- label: this.emissionCategoryService.categoryLabel(category),
7005
- values: this.labels().map((_, i) => sum(items.map(item => item.values[i]))),
7006
- color: listColor(category, index),
7007
- includedItems: items.map((item, i) => ({
7008
- ...item,
7009
- color: listColor(item.id, i)
7010
- }))
7011
- }));
7012
- }, ...(ngDevMode ? [{ debugName: "displayData" }] : []));
7078
+ this.displayData = computed(() => groupDataByCategory(this.data(), this.labels(), this.category(), this.emissionCategoryService), ...(ngDevMode ? [{ debugName: "displayData" }] : []));
7013
7079
  this.defaultConfig = computed(() => ({
7014
7080
  options: {
7015
7081
  onClick: (event, activeElements, chart) => this.onItemClick(event, activeElements, chart),
@@ -8933,16 +8999,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
8933
8999
 
8934
9000
  class GuideOverlayService {
8935
9001
  constructor() {
9002
+ this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
8936
9003
  this.message = signal(null, ...(ngDevMode ? [{ debugName: "message" }] : []));
8937
9004
  this.messageListener = ({ data }) => {
8938
9005
  if (data.namespace == guideNamespace) {
8939
9006
  this.message.set(data);
8940
9007
  }
8941
9008
  };
8942
- window.addEventListener('message', this.messageListener);
9009
+ // `postMessage` communication only exists in the browser; skip on the server (SSR).
9010
+ if (this.isBrowser) {
9011
+ window.addEventListener('message', this.messageListener);
9012
+ }
8943
9013
  }
8944
9014
  ngOnDestroy() {
8945
- window.removeEventListener('message', this.messageListener);
9015
+ if (this.isBrowser) {
9016
+ window.removeEventListener('message', this.messageListener);
9017
+ }
8946
9018
  }
8947
9019
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: GuideOverlayService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
8948
9020
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: GuideOverlayService, providedIn: 'root' }); }
@@ -9290,6 +9362,10 @@ const isMissing = (value) => value === undefined || value === null || value ===
9290
9362
  // the result symbol (left-hand side) - it is the model's output, not an input that "was not logged", so
9291
9363
  // it is excluded from the missing-values notice (e.g. a failed model has no result value)
9292
9364
  const resultKey = 'value';
9365
+ // the reserved table key a `\sum` over per-contributor products binds to (e.g. `[contributions:value]`).
9366
+ // its rows come from the ImpactAssessment's contribution data (register_contribution), not the jlog, so
9367
+ // these "characterisation = Σ contributions" models render fully without duplicating values into the logs
9368
+ const contributionsKey = 'contributions';
9293
9369
  // the "value formula" computes the model's result (`key: "value"`); any others are sub-formulas that
9294
9370
  // compute its components (possibly chained: a sub-formula's result feeds another sub-formula)
9295
9371
  const isValueFormula = (formula) => formula.bindings.some(binding => binding.key === resultKey);
@@ -9352,15 +9428,42 @@ class NodeLogsModelsFormulaComponent {
9352
9428
  // the value the model returned (the recalculated node value): the result symbol (`key: "value"`) is
9353
9429
  // usually not logged, so it falls back to this
9354
9430
  this.value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
9431
+ // the ImpactAssessment the model ran on: lets a contribution-based `\sum` pull its per-contributor
9432
+ // summands from the stored contribution data instead of duplicating them into the jlog
9433
+ this.node = input(...(ngDevMode ? [undefined, { debugName: "node" }] : []));
9434
+ this.nodeService = inject(HeNodeService);
9355
9435
  // show the substituted formula (values in place of symbols) rather than the raw symbolic one
9356
9436
  this.substituted = signal(false, ...(ngDevMode ? [{ debugName: "substituted" }] : []));
9357
9437
  // a unique id per instance so the switch label only toggles its own checkbox (several popovers can co-exist)
9358
9438
  this.toggleId = uuid('formulaSubstituted-');
9359
9439
  this.formulas = computed(() => getModelFormulas(this.model()), ...(ngDevMode ? [{ debugName: "formulas" }] : []));
9440
+ // whether any formula sums over per-contributor products (bound to the reserved `contributions` table)
9441
+ this.hasContributionBinding = computed(() => this.formulas().some(formula => formula.bindings.some(binding => binding.key === contributionsKey)), ...(ngDevMode ? [{ debugName: "hasContributionBinding" }] : []));
9442
+ // only fetch contributions when a formula actually needs them and we have an ImpactAssessment to fetch for
9443
+ this.contributionResource = rxResource({
9444
+ params: () => (this.hasContributionBinding() && this.node() ? { node: this.node() } : undefined),
9445
+ stream: ({ params: { node } }) => this.nodeService.getContributions$(node)
9446
+ });
9447
+ // the contributions to this indicator term, grouped by method model (empty until loaded / when N/A)
9448
+ this.termContributions = computed(() => {
9449
+ const node = this.node();
9450
+ const contributions = this.contributionResource.value();
9451
+ if (!node || !contributions)
9452
+ return {};
9453
+ return simplifyContributions(node, contributions)[this.model()?.term ?? ''] ?? {};
9454
+ }, ...(ngDevMode ? [{ debugName: "termContributions" }] : []));
9455
+ // the per-contributor summands for this model, shaped as `\sum` rows: { id: contributor term, value }
9456
+ this.contributionRows = computed(() => {
9457
+ const byIndicator = this.termContributions()[this.model()?.model ?? ''] ?? {};
9458
+ return Object.entries(byIndicator).map(([id, value]) => ({ id, value }));
9459
+ }, ...(ngDevMode ? [{ debugName: "contributionRows" }] : []));
9360
9460
  this.values = computed(() => {
9361
9461
  const values = logValues(this.logs());
9362
9462
  // the result (`value`) is usually the model's returned value rather than a logged field
9363
- return isMissing(values.value) && !isMissing(this.value()) ? { ...values, value: this.value() } : values;
9463
+ const withResult = isMissing(values.value) && !isMissing(this.value()) ? { ...values, value: this.value() } : values;
9464
+ // feed a contribution-based `\sum` from the stored contributions (nothing to merge for other formulas)
9465
+ const rows = this.contributionRows();
9466
+ return rows.length ? { ...withResult, [contributionsKey]: rows } : withResult;
9364
9467
  }, ...(ngDevMode ? [{ debugName: "values" }] : []));
9365
9468
  this.valueFormula = computed(() => this.formulas().find(isValueFormula), ...(ngDevMode ? [{ debugName: "valueFormula" }] : []));
9366
9469
  // the value formula's own symbol keys - kept symbolic in the sub-formulas (they are substituted only in
@@ -9411,12 +9514,12 @@ class NodeLogsModelsFormulaComponent {
9411
9514
  this.substituted.update(value => !value);
9412
9515
  }
9413
9516
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsModelsFormulaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9414
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeLogsModelsFormulaComponent, isStandalone: true, selector: "he-node-logs-models-formula", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, logs: { classPropertyName: "logs", publicName: "logs", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (formulas().length) {\n <div class=\"formula-block is-mb-2 has-text-white\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span class=\"is-uppercase has-text-weight-semibold\">Formula{{ formulas().length > 1 ? 's' : '' }}</span>\n <div class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : 'No logged values to substitute'\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n </div>\n\n @for (item of renderedFormulas(); track $index) {\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.description) {\n <span>:</span>\n }\n </span>\n @if (variable.description) {\n <markdown class=\"is-inline-block is-italic | formula-variable-desc\" [data]=\"variable.description\" />\n }\n @if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n}\n", styles: [".formula-block{border-bottom:1px solid rgba(255,255,255,.2)}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-variable-desc ::ng-deep *{display:inline;margin:0}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"], dependencies: [{ kind: "directive", type: KatexDirective, selector: "[heKatex]", inputs: ["heKatex", "heKatexInline"] }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9517
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeLogsModelsFormulaComponent, isStandalone: true, selector: "he-node-logs-models-formula", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, logs: { classPropertyName: "logs", publicName: "logs", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (formulas().length) {\n <div class=\"formula-block is-mb-2 has-text-white\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span class=\"is-uppercase has-text-weight-semibold\">Formula{{ formulas().length > 1 ? 's' : '' }}</span>\n <div class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : 'No logged values to substitute'\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n </div>\n\n @for (item of renderedFormulas(); track $index) {\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.description) {\n <span>:</span>\n }\n </span>\n @if (variable.description) {\n <markdown class=\"is-inline-block is-italic | formula-variable-desc\" [data]=\"variable.description\" />\n }\n @if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n}\n", styles: [".formula-block{border-bottom:1px solid rgba(255,255,255,.2)}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-variable-desc ::ng-deep *{display:inline;margin:0}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"], dependencies: [{ kind: "directive", type: KatexDirective, selector: "[heKatex]", inputs: ["heKatex", "heKatexInline"] }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9415
9518
  }
9416
9519
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsModelsFormulaComponent, decorators: [{
9417
9520
  type: Component$1,
9418
9521
  args: [{ selector: 'he-node-logs-models-formula', changeDetection: ChangeDetectionStrategy.OnPush, imports: [KatexDirective, MarkdownComponent], template: "@if (formulas().length) {\n <div class=\"formula-block is-mb-2 has-text-white\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span class=\"is-uppercase has-text-weight-semibold\">Formula{{ formulas().length > 1 ? 's' : '' }}</span>\n <div class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : 'No logged values to substitute'\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n </div>\n\n @for (item of renderedFormulas(); track $index) {\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.description) {\n <span>:</span>\n }\n </span>\n @if (variable.description) {\n <markdown class=\"is-inline-block is-italic | formula-variable-desc\" [data]=\"variable.description\" />\n }\n @if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n}\n", styles: [".formula-block{border-bottom:1px solid rgba(255,255,255,.2)}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-variable-desc ::ng-deep *{display:inline;margin:0}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"] }]
9419
- }], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: false }] }], logs: [{ type: i0.Input, args: [{ isSignal: true, alias: "logs", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
9522
+ }], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: false }] }], logs: [{ type: i0.Input, args: [{ isSignal: true, alias: "logs", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }] } });
9420
9523
 
9421
9524
  class NodeLogsModelsDetailsComponent {
9422
9525
  constructor() {
@@ -9460,7 +9563,7 @@ class NodeLogsModelsDetailsComponent {
9460
9563
  this.openKeys.set(next);
9461
9564
  }
9462
9565
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsModelsDetailsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9463
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeLogsModelsDetailsComponent, isStandalone: true, selector: "he-node-logs-models-details", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, hasContributions: { classPropertyName: "hasContributions", publicName: "hasContributions", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@for (section of sections(); track section.key; let last = $last) {\n <div class=\"log-section\" [class.has-border-bottom]=\"!last || isOpen(section.key)\">\n @if (section.key === 'formula' && isOpen('formula')) {\n <!-- the formula component renders its own \"Formula BETA\" title + Raw/Substituted switch on one\n line, so keep the toggle chevron inline with it rather than adding a duplicate header -->\n <div class=\"log-section-header is-flex is-align-items-flex-start is-gap-4\">\n <a class=\"has-text-white\" (click)=\"toggle('formula')\">\n <he-svg-icon name=\"chevron-down\" size=\"16\" />\n </a>\n <div class=\"is-flex-grow-1\">\n <he-node-logs-models-formula [model]=\"model().model\" [logs]=\"model().logs\" [value]=\"value()\" />\n </div>\n </div>\n } @else {\n <a\n class=\"log-section-header is-flex is-align-items-center is-gap-4 has-text-white is-size-8 is-uppercase has-text-weight-semibold\"\n (click)=\"toggle(section.key)\">\n <he-svg-icon [name]=\"isOpen(section.key) ? 'chevron-down' : 'chevron-right'\" size=\"16\" />\n <span>{{ section.label }}</span>\n </a>\n\n @if (isOpen(section.key)) {\n <div class=\"log-section-body is-pb-2\">\n @switch (section.key) {\n @case ('logs') {\n <he-node-logs-models-logs [logs]=\"model().logs\" [renderRaw]=\"false\" />\n }\n @case ('raw') {\n <div class=\"is-flex is-justify-content-flex-end is-mb-1\">\n <he-clipboard clipboardClass=\"is-size-7 is-p-1\" [value]=\"rawJson()\" [hideText]=\"true\" />\n </div>\n <pre class=\"raw-log\">{{ rawJson() }}</pre>\n }\n @case ('contributions') {\n <he-node-logs-models-contributions [node]=\"node()\" [nodeKey]=\"nodeKey()\" [model]=\"model()\" />\n }\n }\n </div>\n }\n }\n </div>\n}\n", styles: [".log-section{border-color:#fff3!important}.log-section-header{cursor:pointer;padding:.5rem 0}.log-section-header ::ng-deep .formula-block{margin-bottom:0;border-bottom:none}.raw-log{max-height:300px;overflow:auto;white-space:pre;font-size:.75rem;background:#00000059;color:inherit;padding:.5rem;border-radius:3px}\n"], dependencies: [{ kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: ClipboardComponent, selector: "he-clipboard", inputs: ["icon", "value", "disabled", "hideText", "hideIcon", "size", "clipboardClass", "tooltipPlacement"] }, { kind: "component", type: NodeLogsModelsFormulaComponent, selector: "he-node-logs-models-formula", inputs: ["model", "logs", "value"] }, { kind: "component", type: NodeLogsModelsLogsComponent, selector: "he-node-logs-models-logs", inputs: ["logs", "renderRaw"] }, { kind: "component", type: NodeLogsModelsContributionsComponent, selector: "he-node-logs-models-contributions", inputs: ["node", "nodeKey", "model"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9566
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeLogsModelsDetailsComponent, isStandalone: true, selector: "he-node-logs-models-details", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, hasContributions: { classPropertyName: "hasContributions", publicName: "hasContributions", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@for (section of sections(); track section.key; let last = $last) {\n <div class=\"log-section\" [class.has-border-bottom]=\"!last || isOpen(section.key)\">\n @if (section.key === 'formula' && isOpen('formula')) {\n <!-- the formula component renders its own \"Formula BETA\" title + Raw/Substituted switch on one\n line, so keep the toggle chevron inline with it rather than adding a duplicate header -->\n <div class=\"log-section-header is-flex is-align-items-flex-start is-gap-4\">\n <a class=\"has-text-white\" (click)=\"toggle('formula')\">\n <he-svg-icon name=\"chevron-down\" size=\"16\" />\n </a>\n <div class=\"is-flex-grow-1\">\n <he-node-logs-models-formula\n [model]=\"model().model\"\n [logs]=\"model().logs\"\n [value]=\"value()\"\n [node]=\"node()\" />\n </div>\n </div>\n } @else {\n <a\n class=\"log-section-header is-flex is-align-items-center is-gap-4 has-text-white is-size-8 is-uppercase has-text-weight-semibold\"\n (click)=\"toggle(section.key)\">\n <he-svg-icon [name]=\"isOpen(section.key) ? 'chevron-down' : 'chevron-right'\" size=\"16\" />\n <span>{{ section.label }}</span>\n </a>\n\n @if (isOpen(section.key)) {\n <div class=\"log-section-body is-pb-2\">\n @switch (section.key) {\n @case ('logs') {\n <he-node-logs-models-logs [logs]=\"model().logs\" [renderRaw]=\"false\" />\n }\n @case ('raw') {\n <div class=\"is-flex is-justify-content-flex-end is-mb-1\">\n <he-clipboard clipboardClass=\"is-size-7 is-p-1\" [value]=\"rawJson()\" [hideText]=\"true\" />\n </div>\n <pre class=\"raw-log\">{{ rawJson() }}</pre>\n }\n @case ('contributions') {\n <he-node-logs-models-contributions [node]=\"node()\" [nodeKey]=\"nodeKey()\" [model]=\"model()\" />\n }\n }\n </div>\n }\n }\n </div>\n}\n", styles: [".log-section{border-color:#fff3!important}.log-section-header{cursor:pointer;padding:.5rem 0}.log-section-header ::ng-deep .formula-block{margin-bottom:0;border-bottom:none}.raw-log{max-height:300px;overflow:auto;white-space:pre;font-size:.75rem;background:#00000059;color:inherit;padding:.5rem;border-radius:3px}\n"], dependencies: [{ kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: ClipboardComponent, selector: "he-clipboard", inputs: ["icon", "value", "disabled", "hideText", "hideIcon", "size", "clipboardClass", "tooltipPlacement"] }, { kind: "component", type: NodeLogsModelsFormulaComponent, selector: "he-node-logs-models-formula", inputs: ["model", "logs", "value", "node"] }, { kind: "component", type: NodeLogsModelsLogsComponent, selector: "he-node-logs-models-logs", inputs: ["logs", "renderRaw"] }, { kind: "component", type: NodeLogsModelsContributionsComponent, selector: "he-node-logs-models-contributions", inputs: ["node", "nodeKey", "model"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9464
9567
  }
9465
9568
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsModelsDetailsComponent, decorators: [{
9466
9569
  type: Component$1,
@@ -9470,7 +9573,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
9470
9573
  NodeLogsModelsFormulaComponent,
9471
9574
  NodeLogsModelsLogsComponent,
9472
9575
  NodeLogsModelsContributionsComponent
9473
- ], template: "@for (section of sections(); track section.key; let last = $last) {\n <div class=\"log-section\" [class.has-border-bottom]=\"!last || isOpen(section.key)\">\n @if (section.key === 'formula' && isOpen('formula')) {\n <!-- the formula component renders its own \"Formula BETA\" title + Raw/Substituted switch on one\n line, so keep the toggle chevron inline with it rather than adding a duplicate header -->\n <div class=\"log-section-header is-flex is-align-items-flex-start is-gap-4\">\n <a class=\"has-text-white\" (click)=\"toggle('formula')\">\n <he-svg-icon name=\"chevron-down\" size=\"16\" />\n </a>\n <div class=\"is-flex-grow-1\">\n <he-node-logs-models-formula [model]=\"model().model\" [logs]=\"model().logs\" [value]=\"value()\" />\n </div>\n </div>\n } @else {\n <a\n class=\"log-section-header is-flex is-align-items-center is-gap-4 has-text-white is-size-8 is-uppercase has-text-weight-semibold\"\n (click)=\"toggle(section.key)\">\n <he-svg-icon [name]=\"isOpen(section.key) ? 'chevron-down' : 'chevron-right'\" size=\"16\" />\n <span>{{ section.label }}</span>\n </a>\n\n @if (isOpen(section.key)) {\n <div class=\"log-section-body is-pb-2\">\n @switch (section.key) {\n @case ('logs') {\n <he-node-logs-models-logs [logs]=\"model().logs\" [renderRaw]=\"false\" />\n }\n @case ('raw') {\n <div class=\"is-flex is-justify-content-flex-end is-mb-1\">\n <he-clipboard clipboardClass=\"is-size-7 is-p-1\" [value]=\"rawJson()\" [hideText]=\"true\" />\n </div>\n <pre class=\"raw-log\">{{ rawJson() }}</pre>\n }\n @case ('contributions') {\n <he-node-logs-models-contributions [node]=\"node()\" [nodeKey]=\"nodeKey()\" [model]=\"model()\" />\n }\n }\n </div>\n }\n }\n </div>\n}\n", styles: [".log-section{border-color:#fff3!important}.log-section-header{cursor:pointer;padding:.5rem 0}.log-section-header ::ng-deep .formula-block{margin-bottom:0;border-bottom:none}.raw-log{max-height:300px;overflow:auto;white-space:pre;font-size:.75rem;background:#00000059;color:inherit;padding:.5rem;border-radius:3px}\n"] }]
9576
+ ], template: "@for (section of sections(); track section.key; let last = $last) {\n <div class=\"log-section\" [class.has-border-bottom]=\"!last || isOpen(section.key)\">\n @if (section.key === 'formula' && isOpen('formula')) {\n <!-- the formula component renders its own \"Formula BETA\" title + Raw/Substituted switch on one\n line, so keep the toggle chevron inline with it rather than adding a duplicate header -->\n <div class=\"log-section-header is-flex is-align-items-flex-start is-gap-4\">\n <a class=\"has-text-white\" (click)=\"toggle('formula')\">\n <he-svg-icon name=\"chevron-down\" size=\"16\" />\n </a>\n <div class=\"is-flex-grow-1\">\n <he-node-logs-models-formula\n [model]=\"model().model\"\n [logs]=\"model().logs\"\n [value]=\"value()\"\n [node]=\"node()\" />\n </div>\n </div>\n } @else {\n <a\n class=\"log-section-header is-flex is-align-items-center is-gap-4 has-text-white is-size-8 is-uppercase has-text-weight-semibold\"\n (click)=\"toggle(section.key)\">\n <he-svg-icon [name]=\"isOpen(section.key) ? 'chevron-down' : 'chevron-right'\" size=\"16\" />\n <span>{{ section.label }}</span>\n </a>\n\n @if (isOpen(section.key)) {\n <div class=\"log-section-body is-pb-2\">\n @switch (section.key) {\n @case ('logs') {\n <he-node-logs-models-logs [logs]=\"model().logs\" [renderRaw]=\"false\" />\n }\n @case ('raw') {\n <div class=\"is-flex is-justify-content-flex-end is-mb-1\">\n <he-clipboard clipboardClass=\"is-size-7 is-p-1\" [value]=\"rawJson()\" [hideText]=\"true\" />\n </div>\n <pre class=\"raw-log\">{{ rawJson() }}</pre>\n }\n @case ('contributions') {\n <he-node-logs-models-contributions [node]=\"node()\" [nodeKey]=\"nodeKey()\" [model]=\"model()\" />\n }\n }\n </div>\n }\n }\n </div>\n}\n", styles: [".log-section{border-color:#fff3!important}.log-section-header{cursor:pointer;padding:.5rem 0}.log-section-header ::ng-deep .formula-block{margin-bottom:0;border-bottom:none}.raw-log{max-height:300px;overflow:auto;white-space:pre;font-size:.75rem;background:#00000059;color:inherit;padding:.5rem;border-radius:3px}\n"] }]
9474
9577
  }], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }], nodeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKey", required: false }] }], hasContributions: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasContributions", required: false }] }] } });
9475
9578
 
9476
9579
  /**
@@ -14234,7 +14337,7 @@ ${JSON.stringify(error)}
14234
14337
 
14235
14338
  ## Details
14236
14339
 
14237
- * Path: [open](${window.location.href})
14340
+ * Path: [open](${typeof window === 'undefined' ? baseUrl() : window.location.href})
14238
14341
  * Stage: \`${fileStatus?.replace('Done', '')?.replace('Error', '')}\`
14239
14342
 
14240
14343
  /label ~"Type::bug"
@@ -14823,7 +14926,7 @@ const mergedNodes = (selection, { tooltipOperator }) => {
14823
14926
  .attr('stroke-width', '2')
14824
14927
  .attr('rx', '3px')
14825
14928
  .attr('ry', '3px')
14826
- .style('user-select', window.innerWidth < 768 ? 'none' : null);
14929
+ .style('user-select', typeof window !== 'undefined' && window.innerWidth < 768 ? 'none' : null);
14827
14930
  selection.filter(d => !d.data.group && d.data.id !== nonLCAIndicatorsId).call(addTooltip, { tooltipOperator });
14828
14931
  selection
14829
14932
  .selectAll('.node-label')
@@ -16695,5 +16798,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
16695
16798
  * Generated bundle index. Do not edit.
16696
16799
  */
16697
16800
 
16698
- export { ARRAY_DELIMITER, ApplyPurePipe, BarChartComponent, BibliographiesSearchConfirmComponent, BlankNodeStateComponent, BlankNodeStateNoticeComponent, BlankNodeValueDeltaComponent, CapitalizePipe, ChartComponent, ChartConfigurationDirective, ChartExportButtonComponent, ChartTooltipComponent, ClickOutsideDirective, ClipboardComponent, CollapsibleBoxComponent, CollapsibleBoxStyle, ColorPalette, CompoundDirective, CompoundPipe, ContributionChartComponent, ControlValueAccessor, CycleNodesKeyGroup, CyclesCompletenessComponent, CyclesEmissionsCategoryService, CyclesEmissionsChartComponent, CyclesFunctionalUnitMeasureComponent, CyclesMetadataComponent, CyclesNodesComponent, CyclesNodesTimelineComponent, CyclesResultComponent, DataTableComponent, DefaultPipe, DeltaColour, DistributionChartComponent, DrawerContainerComponent, DurationPipe, EllipsisPipe, EngineModelsLinkComponent, EngineModelsLookupInfoComponent, EngineModelsStageComponent, EngineModelsStageDeepComponent, EngineModelsStageDeepService, EngineModelsVersionInfoComponent, EngineModelsVersionLinkComponent, EngineOrchestratorEditComponent, EngineRequirementsFormComponent, FileSizePipe, FileUploadErrorKeys, FilesErrorSummaryComponent, FilesFormComponent, FilesFormEditableComponent, FilesUploadErrorsComponent, FilterAccordionComponent, GUIDE_ENABLED, GetPipe, GlossaryMigrationFormat, GuideOverlayComponent, HESvgIconComponent, HE_API_BASE_URL, HE_CALCULATIONS_BASE_URL, HE_MAP_LOADED, HeAuthService, HeCommonService, HeEngineService, HeGlossaryService, HeMendeleyService, HeNodeCsvService, HeNodeService, HeNodeStoreService, HeSchemaService, HeSearchService, HeToastService, HorizontalBarChartComponent, HorizontalButtonsGroupComponent, ImpactAssessmentsGraphComponent, ImpactAssessmentsIndicatorBreakdownChartComponent, ImpactAssessmentsIndicatorsChartComponent, ImpactAssessmentsProductsComponent, IsArrayPipe, IsObjectPipe, IssueConfirmComponent, KatexDirective, KeyToLabelPipe, Level, LineChartComponent, LinkKeyValueComponent, LogStatus, LongPressDirective, MAX_RESULTS, MapsDrawingComponent, MapsDrawingConfirmComponent, MaxPipe, MeanPipe, MedianPipe, MendeleySearchResult, MinPipe, MobileShellComponent, NavigationMenuComponent, NoExtPipe, NodeAggregatedComponent, NodeAggregatedInfoComponent, NodeAggregatedQualityScoreComponent, NodeCsvExportConfirmComponent, NodeCsvPreviewComponent, NodeCsvSelectHeadersComponent, NodeIconComponent, NodeJLogModelsComponent, NodeJsonldComponent, NodeJsonldSchemaComponent, NodeKeyState, NodeLinkComponent, NodeLogsFileComponent, NodeLogsModelsComponent, NodeLogsTimeComponent, NodeMissingLookupFactorsComponent, NodeQualityScore, NodeRecommendationsComponent, NodeSelectComponent, NodeValueDetailsComponent, PipelineStagesProgressComponent, PluralizePipe, PopoverComponent, PopoverConfirmComponent, PrecisionPipe, RelatedNodeResult, RemoveMarkdownPipe, RepeatPipe, Repository, ResizedDirective, ResizedEvent, ResponsiveService, SchemaInfoComponent, SchemaVersionLinkComponent, SearchExtendComponent, ShelfDialogComponent, ShellComponent, SiteNodesKeyGroup, SitesManagementChartComponent, SitesMapsComponent, SitesNodesComponent, SkeletonTextComponent, SocialTagsComponent, SortByPipe, SortSelectComponent, SumPipe, TagsInputDirective, Template, TermsPropertyContentComponent, TermsSubClassOfContentComponent, TermsUnitsDescriptionComponent, ThousandSuffixesPipe, ThousandsPipe, TimesPipe, ToastComponent, UncapitalizePipe, addPolygonToFeature, afterBarDrawPlugin, allCountriesQuery, allGroups, allOptions, availableProperties, axisHoverPlugin, backgroundHoverPlugin, baseApiUrl, baseUrl, bottom, buildSummary, bytesSize, calculateCycleDuration, calculateCycleDurationEnabled, calculateCycleStartDate, calculateCycleStartDateEnabled, capitalize, changelogUrl, clustererImage, code, colorToRgba, compoundToHtml, computeKeys, computeTerms, contactUsEmail, contactUsLink, convertToSvg, coordinatesToPoint, copyObject, countGroupVisibleNodes, countriesQuery, createMarker, cropsQuery, d3ellipse, d3wrap, dataPathLabel, dataPathToKey, dataVersionHeader, dataVersionHeaderKey, defaultFeature, defaultLabel, defaultSuggestionType, defaultSvgIconSize, defaultTicksFont, definitionToSchemaType, distinctUntilChangedDeep, downloadFile, downloadPng, downloadSvg, ellipsis, engineGitBaseUrl, engineGitUrl, errorText, evaluateSuccess, exportAsSVG, exportFormats, externalLink, externalNodeLink, fillColor, fillStyle, filterBlankNode$1 as filterBlankNode, filterParams, findConfigModels, findMatchingModel, findModels, findNodeModel, findOrchestratorModel, findProperty, findPropertyById, flatFilterData, flatFilterNode, formatCustomErrorMessage, formatDate, formatError, formatPropertyError, formatter, getColor, getDatesBetween, gitBranch, gitHome, gitlabRawUrl, glossaryBaseUrl, glossaryLink, groupChanged, groupJLogByField, groupJLogByTerm, groupLogsByTerm, groupNodesByTerm, groupdLogsByKey, grouppedKeys, grouppedValueKeys, groupsLogsByFields, guideModelUrl, guideNamespace, handleAPIError, handleGuideEvent, hasError, hasValidationError, hasWarning, hexToRgba, iconSizes, icons, ignoreKeys$2 as ignoreKeys, increaseScaleLimits, initialFilterState, injectResizeEvent$, inputGroupsTermTypes, isAddPropertyEnabled, isChrome, isDateBetween, isEqual, isExternal, isKeyClosedVisible, isKeyHidden, isMaxStage, isMethodModelAllowed, isNonNodeModelKey, isSchemaIri, isScrolledBelow, isState, isTermTypeAllowed, isValidKey, jLogModelCount, keyToDataPath, levels, listColor, listColorContinuous, listColorWithAlpha, loadMapApi, loadSvgSprite, locationQuery, logToCsv$2 as logToCsv, logValueArray, logsKey, lollipopChartPlugin, lookupUrl, mapFilterData, mapsUrl, markerIcon, markerPie, matchAggregatedQuery, matchAggregatedValidatedQuery, matchBoolPrefixQuery, matchCountry, matchExactQuery, matchGlobalRegion, matchId, matchNameNormalized, matchNestedKey, matchPhrasePrefixQuery, matchPhraseQuery, matchPrimaryProductQuery, matchQuery, matchRegex, matchRegion, matchTermType, matchType, maxAreaSize, measurementValue, mergeDataWithHeaders, methodTierOrder, migrationErrorMessage, migrationsUrl, modelCount, modelKeyParams, modelParams, models, multiMatchQuery, nestedProperty, nestingEnabled, nestingTypeEnabled, noValue, nodeAvailableProperties, nodeById, nodeColours$1 as nodeColours, nodeDataState, nodeDataStates, nodeDataVersion, nodeId, nodeIdWithoutDataVersion, nodeIds, nodeLink, nodeLinkEnabled, nodeLinkTypeEnabled, nodeLogsUrl, nodeQualityScoreColor, nodeQualityScoreLevel, nodeQualityScoreMaxDefault, nodeQualityScoreOrder, nodeRequestId, nodeSecondaryColours, nodeToAggregationFilename, nodeType, nodeTypeDataState, nodeTypeIcon, nodeTypeIconSchema, nodeUrl, nodeUrlParams, nodeVersion, nodeVersionKey, nodesByState, nodesByType, numberGte, optionsFromGroup, parentKey, parentProperty, parseColor, parseData, parseDataPath, parseLines, parseMessage, parseNewValue, pluralize, pointToCoordinates, polygonBounds, polygonToCoordinates, polygonToMap, polygonsFromFeature, populateWithTrackIdsFilterData, postGuideEvent, primaryProduct, productsQuery, propertyError, propertyId, recursiveProperties, refToSchemaType, refreshPropertyKeys, regionsQuery, registerChart, repeat, reportIssueLink, reportIssueUrl, safeJSONParse, safeJSONStringify, schemaBaseUrl, schemaDataBaseUrl, schemaLink, schemaRequiredProperties, schemaTypeToDefaultValue, scrollToEl, scrollTop, searchFilterData, searchableTypes, siblingProperty, simplifyContributions, singleProperty, siteTooBig, siteTypeToColor, siteTypeToIcon, sortProperties, sortedDates, strokeColor, strokeStyle, subValueKeys, suggestMatchQuery, suggestQuery, takeAfterViewInit, termLocation, termLocationName, termProperties, termTypeLabel, toSnakeCase, toThousands, typeToNewProperty, typeaheadFocus, uncapitalize, uniqueDatesBetween, updateProperties, valueLink, valueToString, valueTypeToDefault, valueValue, waitFor, wildcardQuery };
16801
+ export { ARRAY_DELIMITER, ApplyPurePipe, BarChartComponent, BibliographiesSearchConfirmComponent, BlankNodeStateComponent, BlankNodeStateNoticeComponent, BlankNodeValueDeltaComponent, CapitalizePipe, ChartComponent, ChartConfigurationDirective, ChartExportButtonComponent, ChartTooltipComponent, ClickOutsideDirective, ClipboardComponent, CollapsibleBoxComponent, CollapsibleBoxStyle, ColorPalette, CompoundDirective, CompoundPipe, ContributionChartComponent, ControlValueAccessor, CycleNodesKeyGroup, CyclesCompletenessComponent, CyclesEmissionsCategoryService, CyclesEmissionsChartComponent, CyclesFunctionalUnitMeasureComponent, CyclesMetadataComponent, CyclesNodesComponent, CyclesNodesTimelineComponent, CyclesResultComponent, DataTableComponent, DefaultPipe, DeltaColour, DistributionChartComponent, DrawerContainerComponent, DurationPipe, EllipsisPipe, EngineModelsLinkComponent, EngineModelsLookupInfoComponent, EngineModelsStageComponent, EngineModelsStageDeepComponent, EngineModelsStageDeepService, EngineModelsVersionInfoComponent, EngineModelsVersionLinkComponent, EngineOrchestratorEditComponent, EngineRequirementsFormComponent, FileSizePipe, FileUploadErrorKeys, FilesErrorSummaryComponent, FilesFormComponent, FilesFormEditableComponent, FilesUploadErrorsComponent, FilterAccordionComponent, GUIDE_ENABLED, GetPipe, GlossaryMigrationFormat, GuideOverlayComponent, HESvgIconComponent, HE_API_BASE_URL, HE_CALCULATIONS_BASE_URL, HE_MAP_LOADED, HeAuthService, HeCommonService, HeEngineService, HeGlossaryService, HeMendeleyService, HeNodeCsvService, HeNodeService, HeNodeStoreService, HeSchemaService, HeSearchService, HeToastService, HorizontalBarChartComponent, HorizontalButtonsGroupComponent, ImpactAssessmentsGraphComponent, ImpactAssessmentsIndicatorBreakdownChartComponent, ImpactAssessmentsIndicatorsChartComponent, ImpactAssessmentsProductsComponent, IsArrayPipe, IsObjectPipe, IssueConfirmComponent, KatexDirective, KeyToLabelPipe, Level, LineChartComponent, LinkKeyValueComponent, LogStatus, LongPressDirective, MAX_RESULTS, MapsDrawingComponent, MapsDrawingConfirmComponent, MaxPipe, MeanPipe, MedianPipe, MendeleySearchResult, MinPipe, MobileShellComponent, NavigationMenuComponent, NoExtPipe, NodeAggregatedComponent, NodeAggregatedInfoComponent, NodeAggregatedQualityScoreComponent, NodeCsvExportConfirmComponent, NodeCsvPreviewComponent, NodeCsvSelectHeadersComponent, NodeIconComponent, NodeJLogModelsComponent, NodeJsonldComponent, NodeJsonldSchemaComponent, NodeKeyState, NodeLinkComponent, NodeLogsFileComponent, NodeLogsModelsComponent, NodeLogsTimeComponent, NodeMissingLookupFactorsComponent, NodeQualityScore, NodeRecommendationsComponent, NodeSelectComponent, NodeValueDetailsComponent, PipelineStagesProgressComponent, PluralizePipe, PopoverComponent, PopoverConfirmComponent, PrecisionPipe, RelatedNodeResult, RemoveMarkdownPipe, RepeatPipe, Repository, ResizedDirective, ResizedEvent, ResponsiveService, SchemaInfoComponent, SchemaVersionLinkComponent, SearchExtendComponent, ShelfDialogComponent, ShellComponent, SiteNodesKeyGroup, SitesManagementChartComponent, SitesMapsComponent, SitesNodesComponent, SkeletonTextComponent, SocialTagsComponent, SortByPipe, SortSelectComponent, SumPipe, TagsInputDirective, Template, TermsPropertyContentComponent, TermsSubClassOfContentComponent, TermsUnitsDescriptionComponent, ThousandSuffixesPipe, ThousandsPipe, TimesPipe, ToastComponent, UncapitalizePipe, addPolygonToFeature, afterBarDrawPlugin, allCountriesQuery, allGroups, allOptions, availableProperties, axisHoverPlugin, backgroundHoverPlugin, baseApiUrl, baseUrl, bottom, buildSummary, bytesSize, calculateCycleDuration, calculateCycleDurationEnabled, calculateCycleStartDate, calculateCycleStartDateEnabled, capitalize, changelogUrl, clustererImage, code, colorToRgba, compoundToHtml, computeKeys, computeTerms, contactUsEmail, contactUsLink, convertToSvg, coordinatesToPoint, copyObject, countGroupVisibleNodes, countriesQuery, createMarker, cropsQuery, d3ellipse, d3wrap, dataPathLabel, dataPathToKey, dataVersionHeader, dataVersionHeaderKey, defaultFeature, defaultLabel, defaultSuggestionType, defaultSvgIconSize, defaultTicksFont, definitionToSchemaType, distinctUntilChangedDeep, downloadFile, downloadPng, downloadSvg, ellipsis, engineGitBaseUrl, engineGitUrl, errorText, evaluateSuccess, exportAsSVG, exportFormats, externalLink, externalNodeLink, fillColor, fillStyle, filterBlankNode$1 as filterBlankNode, filterParams, findConfigModels, findMatchingModel, findModels, findNodeModel, findOrchestratorModel, findProperty, findPropertyById, flatFilterData, flatFilterNode, formatCustomErrorMessage, formatDate, formatError, formatPropertyError, formatter, getColor, getDatesBetween, gitBranch, gitHome, gitlabRawUrl, glossaryBaseUrl, glossaryLink, groupChanged, groupDataByCategory, groupJLogByField, groupJLogByTerm, groupLogsByTerm, groupNodesByTerm, groupdLogsByKey, grouppedKeys, grouppedValueKeys, groupsLogsByFields, guideModelUrl, guideNamespace, handleAPIError, handleGuideEvent, hasError, hasValidationError, hasWarning, hexToRgba, iconSizes, icons, ignoreKeys$2 as ignoreKeys, increaseScaleLimits, initialFilterState, injectResizeEvent$, inputGroupsTermTypes, isAddPropertyEnabled, isChrome, isDateBetween, isEqual, isExternal, isKeyClosedVisible, isKeyHidden, isMaxStage, isMethodModelAllowed, isNonNodeModelKey, isSchemaIri, isScrolledBelow, isState, isTermTypeAllowed, isValidKey, jLogModelCount, keyToDataPath, levels, listColor, listColorContinuous, listColorWithAlpha, loadMapApi, loadSvgSprite, locationQuery, logToCsv$2 as logToCsv, logValueArray, logsKey, lollipopChartPlugin, lookupUrl, mapFilterData, mapsUrl, markerIcon, markerPie, matchAggregatedQuery, matchAggregatedValidatedQuery, matchBoolPrefixQuery, matchCountry, matchExactQuery, matchGlobalRegion, matchId, matchNameNormalized, matchNestedKey, matchPhrasePrefixQuery, matchPhraseQuery, matchPrimaryProductQuery, matchQuery, matchRegex, matchRegion, matchTermType, matchType, maxAreaSize, measurementValue, mergeDataWithHeaders, methodTierOrder, migrationErrorMessage, migrationsUrl, modelCount, modelKeyParams, modelParams, models, multiMatchQuery, nestedProperty, nestingEnabled, nestingTypeEnabled, noValue, nodeAvailableProperties, nodeById, nodeColours$1 as nodeColours, nodeDataState, nodeDataStates, nodeDataVersion, nodeId, nodeIdWithoutDataVersion, nodeIds, nodeLink, nodeLinkEnabled, nodeLinkTypeEnabled, nodeLogsUrl, nodeQualityScoreColor, nodeQualityScoreLevel, nodeQualityScoreMaxDefault, nodeQualityScoreOrder, nodeRequestId, nodeSecondaryColours, nodeToAggregationFilename, nodeType, nodeTypeDataState, nodeTypeIcon, nodeTypeIconSchema, nodeUrl, nodeUrlParams, nodeVersion, nodeVersionKey, nodesByState, nodesByType, numberGte, optionsFromGroup, parentKey, parentProperty, parseColor, parseData, parseDataPath, parseLines, parseMessage, parseNewValue, pluralize, pointToCoordinates, polygonBounds, polygonToCoordinates, polygonToMap, polygonsFromFeature, populateWithTrackIdsFilterData, postGuideEvent, primaryProduct, productsQuery, propertyError, propertyId, recursiveProperties, refToSchemaType, refreshPropertyKeys, regionsQuery, registerChart, repeat, reportIssueLink, reportIssueUrl, safeJSONParse, safeJSONStringify, schemaBaseUrl, schemaDataBaseUrl, schemaLink, schemaRequiredProperties, schemaTypeToDefaultValue, scrollToEl, scrollTop, searchFilterData, searchableTypes, siblingProperty, simplifyContributions, singleProperty, siteTooBig, siteTypeToColor, siteTypeToIcon, sortProperties, sortedDates, strokeColor, strokeStyle, subValueKeys, suggestMatchQuery, suggestQuery, sumValues, takeAfterViewInit, termLocation, termLocationName, termProperties, termTypeLabel, toSnakeCase, toThousands, typeToNewProperty, typeaheadFocus, uncapitalize, uniqueDatesBetween, updateProperties, valueLink, valueToString, valueTypeToDefault, valueValue, waitFor, wildcardQuery };
16699
16802
  //# sourceMappingURL=hestia-earth-ui-components.mjs.map