@akcelik/strct 0.28.0 → 0.30.0

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,7 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, DOCUMENT, signal, computed, Injectable, input, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgZone, afterNextRender, Directive, model, output, booleanAttribute, HostListener, contentChildren, effect, ApplicationRef, EnvironmentInjector, createComponent, forwardRef, DestroyRef, contentChild, viewChild, viewChildren, TemplateRef, untracked, Renderer2 } from '@angular/core';
3
3
  import { DomSanitizer } from '@angular/platform-browser';
4
- import { DOCUMENT as DOCUMENT$1, NgTemplateOutlet } from '@angular/common';
4
+ import { NgTemplateOutlet, DOCUMENT as DOCUMENT$1 } from '@angular/common';
5
+ import { RouterLink, RouterLinkActive } from '@angular/router';
5
6
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
6
7
 
7
8
  /** All palettes the token system ships with, in display order. */
@@ -1039,16 +1040,90 @@ class StrctRail {
1039
1040
  ariaLabel = input('Primary', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
1040
1041
  /** Emitted when an item is chosen. */
1041
1042
  select = output();
1042
- pick(item) {
1043
+ topItems = computed(() => this.items().filter((i) => i.placement !== 'bottom'), ...(ngDevMode ? [{ debugName: "topItems" }] : /* istanbul ignore next */ []));
1044
+ bottomItems = computed(() => this.items().filter((i) => i.placement === 'bottom'), ...(ngDevMode ? [{ debugName: "bottomItems" }] : /* istanbul ignore next */ []));
1045
+ /** Enabled item with a router destination → renders as `<a routerLink>`. */
1046
+ isRouted(item) {
1047
+ return item.routerLink != null && !item.disabled;
1048
+ }
1049
+ /**
1050
+ * Active state of a router-link item: an explicit `activeId` is authoritative;
1051
+ * without one, the router's own active state drives it.
1052
+ */
1053
+ isActive(item, routerActive) {
1054
+ const id = this.activeId();
1055
+ return id !== null ? item.id === id : routerActive;
1056
+ }
1057
+ pick(item, event) {
1043
1058
  if (item.disabled)
1044
1059
  return;
1060
+ // A modified / non-primary click on a link is the browser's (new tab etc.) —
1061
+ // never ours: no activeId change, no (select).
1062
+ if (event &&
1063
+ (item.routerLink != null || item.href) &&
1064
+ (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) {
1065
+ return;
1066
+ }
1045
1067
  this.activeId.set(item.id);
1046
1068
  this.select.emit(item);
1047
1069
  }
1048
1070
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctRail, deps: [], target: i0.ɵɵFactoryTarget.Component });
1049
1071
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctRail, isStandalone: true, selector: "strct-rail", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, activeId: { classPropertyName: "activeId", publicName: "activeId", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeId: "activeIdChange", collapsed: "collapsedChange", select: "select" }, host: { properties: { "class.strct-rail--collapsed": "collapsed()" }, classAttribute: "strct-rail" }, ngImport: i0, template: `
1050
- <nav class="strct-rail__nav" [attr.aria-label]="ariaLabel()">
1051
- @for (item of items(); track item.id) {
1072
+ <!-- Shared row content: icon, label, trailing indicator, badge / dot. -->
1073
+ <ng-template #rowContent let-item>
1074
+ <strct-icon class="strct-rail__icon" [name]="item.icon" [size]="18" [strokeWidth]="1.6" />
1075
+ @if (!collapsed()) {
1076
+ <span class="strct-rail__label">{{ item.label }}</span>
1077
+ @if (item.trailingIcon) {
1078
+ <strct-icon
1079
+ class="strct-rail__trailing"
1080
+ [name]="item.trailingIcon"
1081
+ [size]="14"
1082
+ [strokeWidth]="1.5"
1083
+ />
1084
+ }
1085
+ }
1086
+ @if (item.badge !== undefined && item.badge !== null && item.badge !== '') {
1087
+ <span
1088
+ class="strct-rail__badge strct-rail__badge--{{ item.badgeStatus ?? 'accent' }}"
1089
+ [class.strct-rail__badge--dot]="collapsed()"
1090
+ >
1091
+ @if (!collapsed()) {
1092
+ {{ item.badge }}
1093
+ }
1094
+ </span>
1095
+ } @else if (item.dot) {
1096
+ <span class="strct-rail__dot strct-rail__dot--{{ item.dotStatus ?? 'accent' }}"></span>
1097
+ }
1098
+ </ng-template>
1099
+
1100
+ <!-- One rail row: a router link, a plain link, or a button (today's default). -->
1101
+ <ng-template #row let-item>
1102
+ @if (isRouted(item)) {
1103
+ <a
1104
+ class="strct-rail__item"
1105
+ [routerLink]="item.routerLink"
1106
+ routerLinkActive
1107
+ #rla="routerLinkActive"
1108
+ [class.strct-rail__item--active]="isActive(item, rla.isActive)"
1109
+ [attr.aria-current]="isActive(item, rla.isActive) ? 'page' : null"
1110
+ [attr.title]="collapsed() ? item.label : null"
1111
+ (click)="pick(item, $event)"
1112
+ >
1113
+ <ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
1114
+ </a>
1115
+ } @else if (item.href && !item.disabled) {
1116
+ <a
1117
+ class="strct-rail__item"
1118
+ [href]="item.href"
1119
+ [class.strct-rail__item--active]="item.id === activeId()"
1120
+ [attr.aria-current]="item.id === activeId() ? 'page' : null"
1121
+ [attr.title]="collapsed() ? item.label : null"
1122
+ (click)="pick(item, $event)"
1123
+ >
1124
+ <ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
1125
+ </a>
1126
+ } @else {
1052
1127
  <button
1053
1128
  type="button"
1054
1129
  class="strct-rail__item"
@@ -1056,24 +1131,26 @@ class StrctRail {
1056
1131
  [disabled]="item.disabled"
1057
1132
  [attr.aria-current]="item.id === activeId() ? 'page' : null"
1058
1133
  [attr.title]="collapsed() ? item.label : null"
1059
- (click)="pick(item)"
1134
+ (click)="pick(item, $event)"
1060
1135
  >
1061
- <strct-icon class="strct-rail__icon" [name]="item.icon" [size]="18" [strokeWidth]="1.6" />
1062
- @if (!collapsed()) {
1063
- <span class="strct-rail__label">{{ item.label }}</span>
1064
- }
1065
- @if (item.badge !== undefined && item.badge !== null && item.badge !== '') {
1066
- <span
1067
- class="strct-rail__badge strct-rail__badge--{{ item.badgeStatus ?? 'accent' }}"
1068
- [class.strct-rail__badge--dot]="collapsed()"
1069
- >
1070
- @if (!collapsed()) {
1071
- {{ item.badge }}
1072
- }
1073
- </span>
1074
- }
1136
+ <ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
1075
1137
  </button>
1076
1138
  }
1139
+ </ng-template>
1140
+
1141
+ <nav class="strct-rail__nav" [attr.aria-label]="ariaLabel()">
1142
+ <div class="strct-rail__group strct-rail__group--top">
1143
+ @for (item of topItems(); track item.id) {
1144
+ <ng-container *ngTemplateOutlet="row; context: { $implicit: item }" />
1145
+ }
1146
+ </div>
1147
+ @if (bottomItems().length) {
1148
+ <div class="strct-rail__group strct-rail__group--bottom">
1149
+ @for (item of bottomItems(); track item.id) {
1150
+ <ng-container *ngTemplateOutlet="row; context: { $implicit: item }" />
1151
+ }
1152
+ </div>
1153
+ }
1077
1154
  </nav>
1078
1155
 
1079
1156
  @if (collapsible()) {
@@ -1090,13 +1167,66 @@ class StrctRail {
1090
1167
  }
1091
1168
  </button>
1092
1169
  }
1093
- `, isInline: true, styles: [".strct-rail{display:flex;flex-direction:column;width:232px;flex-shrink:0;background:var(--bg-1);border-inline-end:1px solid var(--b2);overflow:hidden;transition:width .16s ease}.strct-rail--collapsed{width:60px}.strct-rail__nav{flex:1;display:flex;flex-direction:column;gap:2px;padding:8px;overflow-y:auto}.strct-rail__item{position:relative;display:flex;align-items:center;gap:11px;width:100%;padding:8px 10px;border:0;border-radius:8px;background:transparent;color:var(--t2);cursor:pointer;font-size:13.5px;font-family:var(--font);text-align:start;white-space:nowrap;transition:background .14s ease,color .14s ease}.strct-rail--collapsed .strct-rail__item{justify-content:center;padding:8px}.strct-rail__item:hover:not(:disabled){background:var(--bg-3);color:var(--t1)}.strct-rail__item--active{background:var(--acc-m);color:var(--acc)}.strct-rail__item--active .strct-rail__icon{color:var(--acc)}.strct-rail__item:disabled{opacity:.5;cursor:not-allowed}.strct-rail__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-rail__icon{flex-shrink:0}.strct-rail__label{flex:1;overflow:hidden;text-overflow:ellipsis}.strct-rail__badge{flex-shrink:0;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;line-height:1;border-radius:9px;font-variant-numeric:tabular-nums}.strct-rail__badge--dot{position:absolute;top:5px;right:7px;min-width:8px;width:8px;height:8px;padding:0;border-radius:50%}.strct-rail__badge--accent{background:var(--acc-m);color:var(--acc)}.strct-rail__badge--success{background:var(--success-bg);color:var(--success)}.strct-rail__badge--warning{background:var(--warning-bg);color:var(--warning)}.strct-rail__badge--critical{background:var(--critical-bg);color:var(--critical)}.strct-rail__badge--dot.strct-rail__badge--accent{background:var(--acc)}.strct-rail__badge--dot.strct-rail__badge--success{background:var(--success)}.strct-rail__badge--dot.strct-rail__badge--warning{background:var(--warning)}.strct-rail__badge--dot.strct-rail__badge--critical{background:var(--critical)}.strct-rail__toggle{display:flex;align-items:center;gap:10px;padding:9px 14px;border:0;border-top:1px solid var(--b1);background:transparent;color:var(--t3);cursor:pointer;font-size:12.5px;font-family:var(--font)}.strct-rail--collapsed .strct-rail__toggle{justify-content:center;padding:9px}.strct-rail__toggle:hover{color:var(--t1);background:var(--bg-3)}.strct-rail__toggle-icon{transition:transform .16s ease}.strct-rail:not(.strct-rail--collapsed) .strct-rail__toggle-icon{transform:rotate(180deg)}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1170
+ `, isInline: true, styles: [".strct-rail{display:flex;flex-direction:column;width:232px;flex-shrink:0;background:var(--bg-1);border-inline-end:1px solid var(--b2);overflow:hidden;transition:width .16s ease}.strct-rail--collapsed{width:60px}.strct-rail__nav{flex:1;min-height:0;display:flex;flex-direction:column;padding:8px}.strct-rail__group{display:flex;flex-direction:column;gap:2px}.strct-rail__group--top{flex:1;min-height:0;overflow-y:auto}.strct-rail__group--bottom{margin-top:8px;padding-top:8px;border-top:1px solid var(--b1)}.strct-rail__item{position:relative;display:flex;align-items:center;gap:11px;width:100%;box-sizing:border-box;padding:8px 10px;border:0;border-radius:8px;background:transparent;color:var(--t2);cursor:pointer;font-size:13.5px;font-family:var(--font);text-align:start;text-decoration:none;white-space:nowrap;transition:background .14s ease,color .14s ease}.strct-rail--collapsed .strct-rail__item{justify-content:center;padding:8px}.strct-rail__item:hover:not(:disabled){background:var(--bg-3);color:var(--t1)}.strct-rail__item--active{background:var(--acc-m);color:var(--acc)}.strct-rail__item--active .strct-rail__icon{color:var(--acc)}.strct-rail__item:disabled{opacity:.5;cursor:not-allowed}.strct-rail__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-rail__icon{flex-shrink:0}.strct-rail__label{flex:1;overflow:hidden;text-overflow:ellipsis}.strct-rail__badge{flex-shrink:0;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;line-height:1;border-radius:9px;font-variant-numeric:tabular-nums}.strct-rail__badge--dot{position:absolute;top:5px;right:7px;min-width:8px;width:8px;height:8px;padding:0;border-radius:50%}.strct-rail__badge--accent{background:var(--acc-m);color:var(--acc)}.strct-rail__badge--success{background:var(--success-bg);color:var(--success)}.strct-rail__badge--warning{background:var(--warning-bg);color:var(--warning)}.strct-rail__badge--critical{background:var(--critical-bg);color:var(--critical)}.strct-rail__badge--dot.strct-rail__badge--accent{background:var(--acc)}.strct-rail__badge--dot.strct-rail__badge--success{background:var(--success)}.strct-rail__badge--dot.strct-rail__badge--warning{background:var(--warning)}.strct-rail__badge--dot.strct-rail__badge--critical{background:var(--critical)}.strct-rail__trailing{flex-shrink:0;color:var(--t3)}.strct-rail__dot{flex-shrink:0;width:7px;height:7px;border-radius:50%}.strct-rail--collapsed .strct-rail__dot{position:absolute;top:5px;right:7px}.strct-rail__dot--accent{background:var(--acc)}.strct-rail__dot--success{background:var(--success)}.strct-rail__dot--warning{background:var(--warning)}.strct-rail__dot--critical{background:var(--critical)}.strct-rail__toggle{display:flex;align-items:center;gap:10px;padding:9px 14px;border:0;border-top:1px solid var(--b1);background:transparent;color:var(--t3);cursor:pointer;font-size:12.5px;font-family:var(--font)}.strct-rail--collapsed .strct-rail__toggle{justify-content:center;padding:9px}.strct-rail__toggle:hover{color:var(--t1);background:var(--bg-3)}.strct-rail__toggle-icon{transition:transform .16s ease}.strct-rail:not(.strct-rail--collapsed) .strct-rail__toggle-icon{transform:rotate(180deg)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1094
1171
  }
1095
1172
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctRail, decorators: [{
1096
1173
  type: Component,
1097
- args: [{ selector: 'strct-rail', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
1098
- <nav class="strct-rail__nav" [attr.aria-label]="ariaLabel()">
1099
- @for (item of items(); track item.id) {
1174
+ args: [{ selector: 'strct-rail', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet, RouterLink, RouterLinkActive, StrctIcon], template: `
1175
+ <!-- Shared row content: icon, label, trailing indicator, badge / dot. -->
1176
+ <ng-template #rowContent let-item>
1177
+ <strct-icon class="strct-rail__icon" [name]="item.icon" [size]="18" [strokeWidth]="1.6" />
1178
+ @if (!collapsed()) {
1179
+ <span class="strct-rail__label">{{ item.label }}</span>
1180
+ @if (item.trailingIcon) {
1181
+ <strct-icon
1182
+ class="strct-rail__trailing"
1183
+ [name]="item.trailingIcon"
1184
+ [size]="14"
1185
+ [strokeWidth]="1.5"
1186
+ />
1187
+ }
1188
+ }
1189
+ @if (item.badge !== undefined && item.badge !== null && item.badge !== '') {
1190
+ <span
1191
+ class="strct-rail__badge strct-rail__badge--{{ item.badgeStatus ?? 'accent' }}"
1192
+ [class.strct-rail__badge--dot]="collapsed()"
1193
+ >
1194
+ @if (!collapsed()) {
1195
+ {{ item.badge }}
1196
+ }
1197
+ </span>
1198
+ } @else if (item.dot) {
1199
+ <span class="strct-rail__dot strct-rail__dot--{{ item.dotStatus ?? 'accent' }}"></span>
1200
+ }
1201
+ </ng-template>
1202
+
1203
+ <!-- One rail row: a router link, a plain link, or a button (today's default). -->
1204
+ <ng-template #row let-item>
1205
+ @if (isRouted(item)) {
1206
+ <a
1207
+ class="strct-rail__item"
1208
+ [routerLink]="item.routerLink"
1209
+ routerLinkActive
1210
+ #rla="routerLinkActive"
1211
+ [class.strct-rail__item--active]="isActive(item, rla.isActive)"
1212
+ [attr.aria-current]="isActive(item, rla.isActive) ? 'page' : null"
1213
+ [attr.title]="collapsed() ? item.label : null"
1214
+ (click)="pick(item, $event)"
1215
+ >
1216
+ <ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
1217
+ </a>
1218
+ } @else if (item.href && !item.disabled) {
1219
+ <a
1220
+ class="strct-rail__item"
1221
+ [href]="item.href"
1222
+ [class.strct-rail__item--active]="item.id === activeId()"
1223
+ [attr.aria-current]="item.id === activeId() ? 'page' : null"
1224
+ [attr.title]="collapsed() ? item.label : null"
1225
+ (click)="pick(item, $event)"
1226
+ >
1227
+ <ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
1228
+ </a>
1229
+ } @else {
1100
1230
  <button
1101
1231
  type="button"
1102
1232
  class="strct-rail__item"
@@ -1104,24 +1234,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
1104
1234
  [disabled]="item.disabled"
1105
1235
  [attr.aria-current]="item.id === activeId() ? 'page' : null"
1106
1236
  [attr.title]="collapsed() ? item.label : null"
1107
- (click)="pick(item)"
1237
+ (click)="pick(item, $event)"
1108
1238
  >
1109
- <strct-icon class="strct-rail__icon" [name]="item.icon" [size]="18" [strokeWidth]="1.6" />
1110
- @if (!collapsed()) {
1111
- <span class="strct-rail__label">{{ item.label }}</span>
1112
- }
1113
- @if (item.badge !== undefined && item.badge !== null && item.badge !== '') {
1114
- <span
1115
- class="strct-rail__badge strct-rail__badge--{{ item.badgeStatus ?? 'accent' }}"
1116
- [class.strct-rail__badge--dot]="collapsed()"
1117
- >
1118
- @if (!collapsed()) {
1119
- {{ item.badge }}
1120
- }
1121
- </span>
1122
- }
1239
+ <ng-container *ngTemplateOutlet="rowContent; context: { $implicit: item }" />
1123
1240
  </button>
1124
1241
  }
1242
+ </ng-template>
1243
+
1244
+ <nav class="strct-rail__nav" [attr.aria-label]="ariaLabel()">
1245
+ <div class="strct-rail__group strct-rail__group--top">
1246
+ @for (item of topItems(); track item.id) {
1247
+ <ng-container *ngTemplateOutlet="row; context: { $implicit: item }" />
1248
+ }
1249
+ </div>
1250
+ @if (bottomItems().length) {
1251
+ <div class="strct-rail__group strct-rail__group--bottom">
1252
+ @for (item of bottomItems(); track item.id) {
1253
+ <ng-container *ngTemplateOutlet="row; context: { $implicit: item }" />
1254
+ }
1255
+ </div>
1256
+ }
1125
1257
  </nav>
1126
1258
 
1127
1259
  @if (collapsible()) {
@@ -1141,7 +1273,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
1141
1273
  `, host: {
1142
1274
  class: 'strct-rail',
1143
1275
  '[class.strct-rail--collapsed]': 'collapsed()',
1144
- }, styles: [".strct-rail{display:flex;flex-direction:column;width:232px;flex-shrink:0;background:var(--bg-1);border-inline-end:1px solid var(--b2);overflow:hidden;transition:width .16s ease}.strct-rail--collapsed{width:60px}.strct-rail__nav{flex:1;display:flex;flex-direction:column;gap:2px;padding:8px;overflow-y:auto}.strct-rail__item{position:relative;display:flex;align-items:center;gap:11px;width:100%;padding:8px 10px;border:0;border-radius:8px;background:transparent;color:var(--t2);cursor:pointer;font-size:13.5px;font-family:var(--font);text-align:start;white-space:nowrap;transition:background .14s ease,color .14s ease}.strct-rail--collapsed .strct-rail__item{justify-content:center;padding:8px}.strct-rail__item:hover:not(:disabled){background:var(--bg-3);color:var(--t1)}.strct-rail__item--active{background:var(--acc-m);color:var(--acc)}.strct-rail__item--active .strct-rail__icon{color:var(--acc)}.strct-rail__item:disabled{opacity:.5;cursor:not-allowed}.strct-rail__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-rail__icon{flex-shrink:0}.strct-rail__label{flex:1;overflow:hidden;text-overflow:ellipsis}.strct-rail__badge{flex-shrink:0;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;line-height:1;border-radius:9px;font-variant-numeric:tabular-nums}.strct-rail__badge--dot{position:absolute;top:5px;right:7px;min-width:8px;width:8px;height:8px;padding:0;border-radius:50%}.strct-rail__badge--accent{background:var(--acc-m);color:var(--acc)}.strct-rail__badge--success{background:var(--success-bg);color:var(--success)}.strct-rail__badge--warning{background:var(--warning-bg);color:var(--warning)}.strct-rail__badge--critical{background:var(--critical-bg);color:var(--critical)}.strct-rail__badge--dot.strct-rail__badge--accent{background:var(--acc)}.strct-rail__badge--dot.strct-rail__badge--success{background:var(--success)}.strct-rail__badge--dot.strct-rail__badge--warning{background:var(--warning)}.strct-rail__badge--dot.strct-rail__badge--critical{background:var(--critical)}.strct-rail__toggle{display:flex;align-items:center;gap:10px;padding:9px 14px;border:0;border-top:1px solid var(--b1);background:transparent;color:var(--t3);cursor:pointer;font-size:12.5px;font-family:var(--font)}.strct-rail--collapsed .strct-rail__toggle{justify-content:center;padding:9px}.strct-rail__toggle:hover{color:var(--t1);background:var(--bg-3)}.strct-rail__toggle-icon{transition:transform .16s ease}.strct-rail:not(.strct-rail--collapsed) .strct-rail__toggle-icon{transform:rotate(180deg)}\n"] }]
1276
+ }, styles: [".strct-rail{display:flex;flex-direction:column;width:232px;flex-shrink:0;background:var(--bg-1);border-inline-end:1px solid var(--b2);overflow:hidden;transition:width .16s ease}.strct-rail--collapsed{width:60px}.strct-rail__nav{flex:1;min-height:0;display:flex;flex-direction:column;padding:8px}.strct-rail__group{display:flex;flex-direction:column;gap:2px}.strct-rail__group--top{flex:1;min-height:0;overflow-y:auto}.strct-rail__group--bottom{margin-top:8px;padding-top:8px;border-top:1px solid var(--b1)}.strct-rail__item{position:relative;display:flex;align-items:center;gap:11px;width:100%;box-sizing:border-box;padding:8px 10px;border:0;border-radius:8px;background:transparent;color:var(--t2);cursor:pointer;font-size:13.5px;font-family:var(--font);text-align:start;text-decoration:none;white-space:nowrap;transition:background .14s ease,color .14s ease}.strct-rail--collapsed .strct-rail__item{justify-content:center;padding:8px}.strct-rail__item:hover:not(:disabled){background:var(--bg-3);color:var(--t1)}.strct-rail__item--active{background:var(--acc-m);color:var(--acc)}.strct-rail__item--active .strct-rail__icon{color:var(--acc)}.strct-rail__item:disabled{opacity:.5;cursor:not-allowed}.strct-rail__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-rail__icon{flex-shrink:0}.strct-rail__label{flex:1;overflow:hidden;text-overflow:ellipsis}.strct-rail__badge{flex-shrink:0;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;line-height:1;border-radius:9px;font-variant-numeric:tabular-nums}.strct-rail__badge--dot{position:absolute;top:5px;right:7px;min-width:8px;width:8px;height:8px;padding:0;border-radius:50%}.strct-rail__badge--accent{background:var(--acc-m);color:var(--acc)}.strct-rail__badge--success{background:var(--success-bg);color:var(--success)}.strct-rail__badge--warning{background:var(--warning-bg);color:var(--warning)}.strct-rail__badge--critical{background:var(--critical-bg);color:var(--critical)}.strct-rail__badge--dot.strct-rail__badge--accent{background:var(--acc)}.strct-rail__badge--dot.strct-rail__badge--success{background:var(--success)}.strct-rail__badge--dot.strct-rail__badge--warning{background:var(--warning)}.strct-rail__badge--dot.strct-rail__badge--critical{background:var(--critical)}.strct-rail__trailing{flex-shrink:0;color:var(--t3)}.strct-rail__dot{flex-shrink:0;width:7px;height:7px;border-radius:50%}.strct-rail--collapsed .strct-rail__dot{position:absolute;top:5px;right:7px}.strct-rail__dot--accent{background:var(--acc)}.strct-rail__dot--success{background:var(--success)}.strct-rail__dot--warning{background:var(--warning)}.strct-rail__dot--critical{background:var(--critical)}.strct-rail__toggle{display:flex;align-items:center;gap:10px;padding:9px 14px;border:0;border-top:1px solid var(--b1);background:transparent;color:var(--t3);cursor:pointer;font-size:12.5px;font-family:var(--font)}.strct-rail--collapsed .strct-rail__toggle{justify-content:center;padding:9px}.strct-rail__toggle:hover{color:var(--t1);background:var(--bg-3)}.strct-rail__toggle-icon{transition:transform .16s ease}.strct-rail:not(.strct-rail--collapsed) .strct-rail__toggle-icon{transform:rotate(180deg)}\n"] }]
1145
1277
  }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], activeId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeId", required: false }] }, { type: i0.Output, args: ["activeIdChange"] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], select: [{ type: i0.Output, args: ["select"] }] } });
1146
1278
 
1147
1279
  /**
@@ -3859,6 +3991,25 @@ class StrctSectionMenu {
3859
3991
  />
3860
3992
  }
3861
3993
  <span class="strct-sm__item-label">{{ item.label }}</span>
3994
+ @if (item.trailingIcon) {
3995
+ <strct-icon
3996
+ class="strct-sm__trailing"
3997
+ [name]="item.trailingIcon"
3998
+ [size]="14"
3999
+ [strokeWidth]="1.5"
4000
+ />
4001
+ }
4002
+ @if (item.badge !== undefined && item.badge !== null && item.badge !== '') {
4003
+ <span
4004
+ class="strct-sm__badge strct-sm__badge--{{ item.badgeStatus ?? 'accent' }}"
4005
+ >
4006
+ {{ item.badge }}
4007
+ </span>
4008
+ } @else if (item.dot) {
4009
+ <span
4010
+ class="strct-sm__dot strct-sm__dot--{{ item.dotStatus ?? 'accent' }}"
4011
+ ></span>
4012
+ }
3862
4013
  </button>
3863
4014
  }
3864
4015
  </div>
@@ -3866,7 +4017,7 @@ class StrctSectionMenu {
3866
4017
  </div>
3867
4018
  }
3868
4019
  </nav>
3869
- `, isInline: true, styles: [".strct-sm{display:flex;flex-direction:column;gap:1px}.strct-sm__section--collapsible+.strct-sm__section--collapsible{margin-top:1px}.strct-sm__cat{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--t2);font-size:13px;font-weight:500;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease,color .13s ease}.strct-sm__cat:hover{background:var(--bg-3);color:var(--t1)}.strct-sm__cat:focus-visible,.strct-sm__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-sm__chevron{display:inline-flex;color:var(--t3);flex-shrink:0;transition:transform .15s ease}.strct-sm__chevron--open{transform:rotate(90deg)}.strct-sm__cat-icon{color:var(--t3);flex-shrink:0}.strct-sm__cat-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__cat-static{padding:11px 10px 5px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--t3)}.strct-sm__items{display:flex;flex-direction:column}.strct-sm__item{display:flex;align-items:center;gap:9px;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease}.strct-sm__section--collapsible .strct-sm__item{padding-inline-start:31px}.strct-sm__item:hover:not(:disabled){background:var(--bg-3)}.strct-sm__item--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-sm__item--active .strct-sm__item-icon{color:var(--acc)}.strct-sm__item:disabled{opacity:.5;cursor:not-allowed}.strct-sm__item-icon{color:var(--t2);flex-shrink:0}.strct-sm__item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4020
+ `, isInline: true, styles: [".strct-sm{display:flex;flex-direction:column;gap:1px}.strct-sm__section--collapsible+.strct-sm__section--collapsible{margin-top:1px}.strct-sm__cat{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--t2);font-size:13px;font-weight:500;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease,color .13s ease}.strct-sm__cat:hover{background:var(--bg-3);color:var(--t1)}.strct-sm__cat:focus-visible,.strct-sm__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-sm__chevron{display:inline-flex;color:var(--t3);flex-shrink:0;transition:transform .15s ease}.strct-sm__chevron--open{transform:rotate(90deg)}.strct-sm__cat-icon{color:var(--t3);flex-shrink:0}.strct-sm__cat-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__cat-static{padding:11px 10px 5px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--t3)}.strct-sm__items{display:flex;flex-direction:column}.strct-sm__item{display:flex;align-items:center;gap:9px;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease}.strct-sm__section--collapsible .strct-sm__item{padding-inline-start:31px}.strct-sm__item:hover:not(:disabled){background:var(--bg-3)}.strct-sm__item--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-sm__item--active .strct-sm__item-icon{color:var(--acc)}.strct-sm__item:disabled{opacity:.5;cursor:not-allowed}.strct-sm__item-icon{color:var(--t2);flex-shrink:0}.strct-sm__item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__trailing{flex-shrink:0;color:var(--t3)}.strct-sm__badge{flex-shrink:0;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;line-height:1;border-radius:9px;font-variant-numeric:tabular-nums}.strct-sm__badge--accent{background:var(--acc-m);color:var(--acc)}.strct-sm__badge--success{background:var(--success-bg);color:var(--success)}.strct-sm__badge--warning{background:var(--warning-bg);color:var(--warning)}.strct-sm__badge--critical{background:var(--critical-bg);color:var(--critical)}.strct-sm__dot{flex-shrink:0;width:7px;height:7px;border-radius:50%}.strct-sm__dot--accent{background:var(--acc)}.strct-sm__dot--success{background:var(--success)}.strct-sm__dot--warning{background:var(--warning)}.strct-sm__dot--critical{background:var(--critical)}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
3870
4021
  }
3871
4022
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSectionMenu, decorators: [{
3872
4023
  type: Component,
@@ -3918,6 +4069,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
3918
4069
  />
3919
4070
  }
3920
4071
  <span class="strct-sm__item-label">{{ item.label }}</span>
4072
+ @if (item.trailingIcon) {
4073
+ <strct-icon
4074
+ class="strct-sm__trailing"
4075
+ [name]="item.trailingIcon"
4076
+ [size]="14"
4077
+ [strokeWidth]="1.5"
4078
+ />
4079
+ }
4080
+ @if (item.badge !== undefined && item.badge !== null && item.badge !== '') {
4081
+ <span
4082
+ class="strct-sm__badge strct-sm__badge--{{ item.badgeStatus ?? 'accent' }}"
4083
+ >
4084
+ {{ item.badge }}
4085
+ </span>
4086
+ } @else if (item.dot) {
4087
+ <span
4088
+ class="strct-sm__dot strct-sm__dot--{{ item.dotStatus ?? 'accent' }}"
4089
+ ></span>
4090
+ }
3921
4091
  </button>
3922
4092
  }
3923
4093
  </div>
@@ -3925,7 +4095,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
3925
4095
  </div>
3926
4096
  }
3927
4097
  </nav>
3928
- `, host: { class: 'strct-sm-host' }, styles: [".strct-sm{display:flex;flex-direction:column;gap:1px}.strct-sm__section--collapsible+.strct-sm__section--collapsible{margin-top:1px}.strct-sm__cat{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--t2);font-size:13px;font-weight:500;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease,color .13s ease}.strct-sm__cat:hover{background:var(--bg-3);color:var(--t1)}.strct-sm__cat:focus-visible,.strct-sm__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-sm__chevron{display:inline-flex;color:var(--t3);flex-shrink:0;transition:transform .15s ease}.strct-sm__chevron--open{transform:rotate(90deg)}.strct-sm__cat-icon{color:var(--t3);flex-shrink:0}.strct-sm__cat-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__cat-static{padding:11px 10px 5px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--t3)}.strct-sm__items{display:flex;flex-direction:column}.strct-sm__item{display:flex;align-items:center;gap:9px;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease}.strct-sm__section--collapsible .strct-sm__item{padding-inline-start:31px}.strct-sm__item:hover:not(:disabled){background:var(--bg-3)}.strct-sm__item--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-sm__item--active .strct-sm__item-icon{color:var(--acc)}.strct-sm__item:disabled{opacity:.5;cursor:not-allowed}.strct-sm__item-icon{color:var(--t2);flex-shrink:0}.strct-sm__item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n"] }]
4098
+ `, host: { class: 'strct-sm-host' }, styles: [".strct-sm{display:flex;flex-direction:column;gap:1px}.strct-sm__section--collapsible+.strct-sm__section--collapsible{margin-top:1px}.strct-sm__cat{display:flex;align-items:center;gap:8px;width:100%;padding:7px 9px;border:0;border-radius:6px;background:transparent;color:var(--t2);font-size:13px;font-weight:500;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease,color .13s ease}.strct-sm__cat:hover{background:var(--bg-3);color:var(--t1)}.strct-sm__cat:focus-visible,.strct-sm__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-sm__chevron{display:inline-flex;color:var(--t3);flex-shrink:0;transition:transform .15s ease}.strct-sm__chevron--open{transform:rotate(90deg)}.strct-sm__cat-icon{color:var(--t3);flex-shrink:0}.strct-sm__cat-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__cat-static{padding:11px 10px 5px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--t3)}.strct-sm__items{display:flex;flex-direction:column}.strct-sm__item{display:flex;align-items:center;gap:9px;width:100%;padding:7px 10px;border:0;border-radius:6px;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);cursor:pointer;text-align:start;transition:background .13s ease}.strct-sm__section--collapsible .strct-sm__item{padding-inline-start:31px}.strct-sm__item:hover:not(:disabled){background:var(--bg-3)}.strct-sm__item--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-sm__item--active .strct-sm__item-icon{color:var(--acc)}.strct-sm__item:disabled{opacity:.5;cursor:not-allowed}.strct-sm__item-icon{color:var(--t2);flex-shrink:0}.strct-sm__item-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-sm__trailing{flex-shrink:0;color:var(--t3)}.strct-sm__badge{flex-shrink:0;min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;line-height:1;border-radius:9px;font-variant-numeric:tabular-nums}.strct-sm__badge--accent{background:var(--acc-m);color:var(--acc)}.strct-sm__badge--success{background:var(--success-bg);color:var(--success)}.strct-sm__badge--warning{background:var(--warning-bg);color:var(--warning)}.strct-sm__badge--critical{background:var(--critical-bg);color:var(--critical)}.strct-sm__dot{flex-shrink:0;width:7px;height:7px;border-radius:50%}.strct-sm__dot--accent{background:var(--acc)}.strct-sm__dot--success{background:var(--success)}.strct-sm__dot--warning{background:var(--warning)}.strct-sm__dot--critical{background:var(--critical)}\n"] }]
3929
4099
  }], propDecorators: { sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: true }] }], activeId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeId", required: false }] }, { type: i0.Output, args: ["activeIdChange"] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], showIcons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showIcons", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], select: [{ type: i0.Output, args: ["select"] }] } });
3930
4100
 
3931
4101
  /**
@@ -8330,6 +8500,64 @@ function smoothPath(p) {
8330
8500
  function pathFor(pts, curve) {
8331
8501
  return curve === 'linear' ? linearPath(pts) : curve === 'step' ? stepPath(pts) : smoothPath(pts);
8332
8502
  }
8503
+ /** A real value — `null` and `NaN` both mark a data gap. */
8504
+ function isVal(v) {
8505
+ return v != null && !Number.isNaN(v);
8506
+ }
8507
+ /** Split a nullable point array into contiguous non-gap segments. */
8508
+ function segs(pts) {
8509
+ const out = [];
8510
+ let cur = [];
8511
+ for (const p of pts) {
8512
+ if (p)
8513
+ cur.push(p);
8514
+ else if (cur.length) {
8515
+ out.push(cur);
8516
+ cur = [];
8517
+ }
8518
+ }
8519
+ if (cur.length)
8520
+ out.push(cur);
8521
+ return out;
8522
+ }
8523
+ /** Line path that breaks at gaps (one sub-path per segment). */
8524
+ function pathForSegs(pts, curve) {
8525
+ return segs(pts)
8526
+ .map((s) => pathFor(s, curve))
8527
+ .join('');
8528
+ }
8529
+ /** Area path per segment — the fill drops to the baseline at gap edges, never across. */
8530
+ function areaForSegs(pts, curve, base) {
8531
+ return segs(pts)
8532
+ .map((s) => `${pathFor(s, curve)}L${round$1(s[s.length - 1].x)},${round$1(base)}L${round$1(s[0].x)},${round$1(base)}Z`)
8533
+ .join('');
8534
+ }
8535
+ /** Closed band between per-point upper and lower bounds (gap-aware). */
8536
+ function bandPath(upper, lower, curve) {
8537
+ let out = '';
8538
+ let u = [];
8539
+ let l = [];
8540
+ const flush = () => {
8541
+ if (u.length > 1) {
8542
+ const back = pathFor([...l].reverse(), curve).replace(/^M/, 'L');
8543
+ out += pathFor(u, curve) + back + 'Z';
8544
+ }
8545
+ u = [];
8546
+ l = [];
8547
+ };
8548
+ for (let i = 0; i < upper.length; i++) {
8549
+ const up = upper[i];
8550
+ const lo = lower[i];
8551
+ if (up && lo) {
8552
+ u.push(up);
8553
+ l.push(lo);
8554
+ }
8555
+ else
8556
+ flush();
8557
+ }
8558
+ flush();
8559
+ return out;
8560
+ }
8333
8561
  /**
8334
8562
  * Single- or multi-series chart (line / area / bar). Dependency-free SVG,
8335
8563
  * token-coloured.
@@ -8346,7 +8574,7 @@ function pathFor(pts, curve) {
8346
8574
  * <strct-chart [series]="[{data:inArr,label:'In'},{data:outArr,label:'Out'}]" legend />
8347
8575
  */
8348
8576
  class StrctChart {
8349
- /** Single-series data (or use `series`). */
8577
+ /** Single-series data (or use `series`). `null` / `NaN` marks a gap — the line breaks. */
8350
8578
  data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
8351
8579
  /** Multiple series; when set it takes precedence over `data`. */
8352
8580
  series = input(null, ...(ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []));
@@ -8404,6 +8632,28 @@ class StrctChart {
8404
8632
  * `[valueFormat]="v => v.toFixed(3) + ' %'"`. When null, the raw value is shown.
8405
8633
  */
8406
8634
  valueFormat = input(null, ...(ngDevMode ? [{ debugName: "valueFormat" }] : /* istanbul ignore next */ []));
8635
+ /** Vertical event / annotation markers ("alarm raised", "deploy", …). */
8636
+ annotations = input([], ...(ngDevMode ? [{ debugName: "annotations" }] : /* istanbul ignore next */ []));
8637
+ /**
8638
+ * Drive the crosshair externally (e.g. mirror a sibling chart's hover for a
8639
+ * vCenter-style synced dashboard). A local pointer wins while over this chart.
8640
+ */
8641
+ activeIndex = input(null, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
8642
+ /** Drag-select a range; the selection is emitted through `brushChange`. */
8643
+ brush = input(false, { ...(ngDevMode ? { debugName: "brush" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
8644
+ /**
8645
+ * Drag-select **zooms into** the selected range (implies `brush`).
8646
+ * Double-click, Escape or the reset chip zooms back out.
8647
+ */
8648
+ zoom = input(false, { ...(ngDevMode ? { debugName: "zoom" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
8649
+ /** Tooltip text for a gap (null) point. */
8650
+ gapText = input('no data', ...(ngDevMode ? [{ debugName: "gapText" }] : /* istanbul ignore next */ []));
8651
+ /** Accessible label of the reset-zoom chip (localizable). */
8652
+ resetLabel = input('Reset zoom', ...(ngDevMode ? [{ debugName: "resetLabel" }] : /* istanbul ignore next */ []));
8653
+ /** Emits the hovered point index (or null on leave) — wire cross-chart sync with it. */
8654
+ hoverIndex = output();
8655
+ /** Emits the brushed [startIndex, endIndex] (inclusive), or null when cleared. */
8656
+ brushChange = output();
8407
8657
  pad = PAD;
8408
8658
  color = computed(() => COLOR$1[this.status()], ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
8409
8659
  showArea = computed(() => this.area() || this.type() === 'area', ...(ngDevMode ? [{ debugName: "showArea" }] : /* istanbul ignore next */ []));
@@ -8420,8 +8670,22 @@ class StrctChart {
8420
8670
  firstData = true;
8421
8671
  /** Reactive OS motion preference (tracks live changes, not just first load). */
8422
8672
  reduceMotion = signal(typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches, ...(ngDevMode ? [{ debugName: "reduceMotion" }] : /* istanbul ignore next */ []));
8423
- // Hover state.
8673
+ // Hover state. `hoverIdx` is the local pointer/keyboard index; `dispIdx` is
8674
+ // what actually renders — local wins, else the externally driven activeIndex.
8424
8675
  hoverIdx = signal(null, ...(ngDevMode ? [{ debugName: "hoverIdx" }] : /* istanbul ignore next */ []));
8676
+ dispIdx = computed(() => {
8677
+ const local = this.hoverIdx();
8678
+ const i = local ?? this.activeIndex();
8679
+ if (i == null)
8680
+ return null;
8681
+ const [s, e] = this.domain();
8682
+ return i >= s && i <= e ? i : null;
8683
+ }, ...(ngDevMode ? [{ debugName: "dispIdx" }] : /* istanbul ignore next */ []));
8684
+ // Zoom / brush state (indices are always in the full-data domain).
8685
+ viewRange = signal(null, ...(ngDevMode ? [{ debugName: "viewRange" }] : /* istanbul ignore next */ []));
8686
+ brushDrag = signal(null, ...(ngDevMode ? [{ debugName: "brushDrag" }] : /* istanbul ignore next */ []));
8687
+ brushEnabled = computed(() => this.brush() || this.zoom(), ...(ngDevMode ? [{ debugName: "brushEnabled" }] : /* istanbul ignore next */ []));
8688
+ zoomed = computed(() => this.viewRange() !== null, ...(ngDevMode ? [{ debugName: "zoomed" }] : /* istanbul ignore next */ []));
8425
8689
  constructor() {
8426
8690
  const destroyRef = inject(DestroyRef);
8427
8691
  if (typeof matchMedia !== 'undefined') {
@@ -8481,6 +8745,16 @@ class StrctChart {
8481
8745
  isEmpty = computed(() => this.allData().every((d) => d.length === 0), ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
8482
8746
  /** Number of x slots. */
8483
8747
  nx = computed(() => Math.max(1, ...this.allData().map((d) => d.length)), ...(ngDevMode ? [{ debugName: "nx" }] : /* istanbul ignore next */ []));
8748
+ /** Visible index window: the brush-zoom range, or the full data. */
8749
+ domain = computed(() => {
8750
+ const n = this.nx();
8751
+ const vr = this.viewRange();
8752
+ if (!vr)
8753
+ return [0, n - 1];
8754
+ const s = Math.max(0, Math.min(vr[0], n - 1));
8755
+ const e = Math.max(s, Math.min(vr[1], n - 1));
8756
+ return e > s ? [s, e] : [0, n - 1];
8757
+ }, ...(ngDevMode ? [{ debugName: "domain" }] : /* istanbul ignore next */ []));
8484
8758
  /** Left padding: wider when the y-axis labels are shown. */
8485
8759
  pl = computed(() => (this.yAxis() ? Y_AXIS_GUTTER : PAD.l), ...(ngDevMode ? [{ debugName: "pl" }] : /* istanbul ignore next */ []));
8486
8760
  chartW() {
@@ -8489,16 +8763,46 @@ class StrctChart {
8489
8763
  chartH() {
8490
8764
  return this.height() - PAD.t - PAD.b;
8491
8765
  }
8492
- /** Horizontal gap between two data points, in px. */
8766
+ /** Horizontal gap between two data points, in px (of the visible window). */
8493
8767
  stepX() {
8494
- const n = this.nx();
8495
- return n > 1 ? this.chartW() / (n - 1) : 0;
8768
+ const [s, e] = this.domain();
8769
+ return e > s ? this.chartW() / (e - s) : 0;
8496
8770
  }
8771
+ /** Real values inside the visible window (multi-aware; bands included). */
8772
+ visibleValues = computed(() => {
8773
+ const [s, e] = this.domain();
8774
+ const out = [];
8775
+ const push = (v) => {
8776
+ if (isVal(v))
8777
+ out.push(v);
8778
+ };
8779
+ const ser = this.seriesResolved();
8780
+ if (ser) {
8781
+ const N = this.nx();
8782
+ for (const x of ser) {
8783
+ const data = x.data ?? [];
8784
+ const offset = N - data.length;
8785
+ for (let gi = Math.max(s, offset); gi <= e; gi++) {
8786
+ const li = gi - offset;
8787
+ if (li < 0 || li >= data.length)
8788
+ continue;
8789
+ push(data[li]);
8790
+ push(x.upper?.[li]);
8791
+ }
8792
+ }
8793
+ }
8794
+ else {
8795
+ const d = this.data();
8796
+ for (let i = s; i <= Math.min(e, d.length - 1); i++)
8797
+ push(d[i]);
8798
+ }
8799
+ return out;
8800
+ }, ...(ngDevMode ? [{ debugName: "visibleValues" }] : /* istanbul ignore next */ []));
8497
8801
  yMax = computed(() => {
8498
8802
  const explicit = this.max();
8499
8803
  if (explicit != null)
8500
8804
  return explicit || 1;
8501
- const m = Math.max(0, ...this.allData().flat());
8805
+ const m = Math.max(0, ...this.visibleValues());
8502
8806
  return m === 0 ? 1 : m * 1.1;
8503
8807
  }, ...(ngDevMode ? [{ debugName: "yMax" }] : /* istanbul ignore next */ []));
8504
8808
  yMin = computed(() => this.min() ?? 0, ...(ngDevMode ? [{ debugName: "yMin" }] : /* istanbul ignore next */ []));
@@ -8509,50 +8813,46 @@ class StrctChart {
8509
8813
  const c = Math.max(lo, Math.min(hi, v));
8510
8814
  return PAD.t + (1 - (c - lo) / range) * this.chartH();
8511
8815
  }
8512
- // ── Single-series geometry (unchanged output) ─────────────────
8816
+ // ── Single-series geometry (gap-aware; null keeps its x-slot) ──
8513
8817
  points = computed(() => {
8514
8818
  const d = this.data();
8515
- const step = this.stepX();
8516
- const pl = this.pl();
8517
- return d.map((v, i) => ({ x: pl + i * step, y: this.yOf(v) }));
8819
+ return d.map((v, i) => (isVal(v) ? { x: this.xOf(i), y: this.yOf(v) } : null));
8518
8820
  }, ...(ngDevMode ? [{ debugName: "points" }] : /* istanbul ignore next */ []));
8821
+ /** Non-gap points only (dot rendering). */
8822
+ dotPts = computed(() => this.points().filter((p) => p !== null), ...(ngDevMode ? [{ debugName: "dotPts" }] : /* istanbul ignore next */ []));
8519
8823
  head = computed(() => {
8520
8824
  const p = this.points();
8521
- return p.length ? p[p.length - 1] : null;
8825
+ for (let i = p.length - 1; i >= 0; i--)
8826
+ if (p[i])
8827
+ return p[i];
8828
+ return null;
8522
8829
  }, ...(ngDevMode ? [{ debugName: "head" }] : /* istanbul ignore next */ []));
8523
8830
  plotPoints = computed(() => {
8524
8831
  const p = this.points();
8525
- if (!this.live() || p.length < 2)
8832
+ if (!this.live() || p.length < 2 || !p[0])
8526
8833
  return p;
8527
8834
  return [{ x: p[0].x - this.stepX(), y: p[0].y }, ...p];
8528
8835
  }, ...(ngDevMode ? [{ debugName: "plotPoints" }] : /* istanbul ignore next */ []));
8529
- linePath = computed(() => pathFor(this.plotPoints(), this.curve()), ...(ngDevMode ? [{ debugName: "linePath" }] : /* istanbul ignore next */ []));
8530
- areaPath = computed(() => {
8531
- const line = this.linePath();
8532
- const p = this.plotPoints();
8533
- if (!line || !p.length)
8534
- return '';
8535
- const base = this.height() - PAD.b;
8536
- return `${line}L${round$1(p[p.length - 1].x)},${round$1(base)}L${round$1(p[0].x)},${round$1(base)}Z`;
8537
- }, ...(ngDevMode ? [{ debugName: "areaPath" }] : /* istanbul ignore next */ []));
8836
+ linePath = computed(() => pathForSegs(this.plotPoints(), this.curve()), ...(ngDevMode ? [{ debugName: "linePath" }] : /* istanbul ignore next */ []));
8837
+ areaPath = computed(() => areaForSegs(this.plotPoints(), this.curve(), this.height() - PAD.b), ...(ngDevMode ? [{ debugName: "areaPath" }] : /* istanbul ignore next */ []));
8538
8838
  // ── Multi-series geometry ─────────────────────────────────────
8539
8839
  multiSeries = computed(() => {
8540
8840
  const s = this.seriesResolved();
8541
8841
  if (!s)
8542
8842
  return [];
8543
8843
  const N = this.nx();
8544
- const step = this.stepX();
8545
- const pl = this.pl();
8546
8844
  const base = this.height() - PAD.b;
8547
8845
  return s.map((x) => {
8548
8846
  const data = x.data ?? [];
8549
8847
  const offset = N - data.length; // right-align shorter series
8550
- const pts = data.map((v, i) => ({ x: pl + (offset + i) * step, y: this.yOf(v) }));
8848
+ const ptOf = (v, i) => isVal(v) ? { x: this.xOf(offset + i), y: this.yOf(v) } : null;
8849
+ const pts = data.map((v, i) => ptOf(v, i));
8551
8850
  const curve = x.curve ?? this.curve();
8552
- const path = pathFor(pts, curve);
8851
+ const path = pathForSegs(pts, curve);
8553
8852
  const area = x.area ?? false;
8554
- const areaPath = area && pts.length
8555
- ? `${path}L${round$1(pts[pts.length - 1].x)},${round$1(base)}L${round$1(pts[0].x)},${round$1(base)}Z`
8853
+ const areaPath = area ? areaForSegs(pts, curve, base) : '';
8854
+ const band = x.lower && x.upper
8855
+ ? bandPath(x.upper.map((v, i) => ptOf(v, i)), x.lower.map((v, i) => ptOf(v, i)), curve)
8556
8856
  : '';
8557
8857
  return {
8558
8858
  color: COLOR$1[x.status ?? this.status()],
@@ -8562,26 +8862,42 @@ class StrctChart {
8562
8862
  pts,
8563
8863
  path,
8564
8864
  areaPath,
8865
+ bandPath: band,
8565
8866
  offset,
8566
8867
  data,
8868
+ lower: x.lower,
8869
+ upper: x.upper,
8567
8870
  };
8568
8871
  });
8569
8872
  }, ...(ngDevMode ? [{ debugName: "multiSeries" }] : /* istanbul ignore next */ []));
8570
8873
  legendItems = computed(() => this.multiSeries()
8571
8874
  .filter((s) => s.label)
8572
8875
  .map((s) => ({ label: s.label, color: s.color, dash: s.dash })), ...(ngDevMode ? [{ debugName: "legendItems" }] : /* istanbul ignore next */ []));
8573
- // ── Bars (single-series only) ─────────────────────────────────
8876
+ // ── Bars (single-series only; gaps render no bar, keep their slot) ──
8574
8877
  bars = computed(() => {
8575
8878
  const d = this.data();
8879
+ const [s, e] = this.domain();
8576
8880
  const chartW = this.chartW();
8577
8881
  const base = this.height() - PAD.b;
8578
- const slot = d.length ? chartW / d.length : chartW;
8882
+ const last = Math.min(e, d.length - 1);
8883
+ const count = Math.max(1, last - s + 1);
8884
+ const slot = chartW / count;
8579
8885
  const w = slot * 0.6;
8580
8886
  const pl = this.pl();
8581
- return d.map((v, i) => {
8887
+ const out = [];
8888
+ for (let i = s; i <= last; i++) {
8889
+ const v = d[i];
8890
+ if (!isVal(v))
8891
+ continue;
8582
8892
  const h = ((Math.max(0, v) - this.yMin()) / (this.yMax() - this.yMin() || 1)) * this.chartH();
8583
- return { x: pl + i * slot + (slot - w) / 2, y: base - Math.max(0, h), w, h: Math.max(0, h) };
8584
- });
8893
+ out.push({
8894
+ x: pl + (i - s) * slot + (slot - w) / 2,
8895
+ y: base - Math.max(0, h),
8896
+ w,
8897
+ h: Math.max(0, h),
8898
+ });
8899
+ }
8900
+ return out;
8585
8901
  }, ...(ngDevMode ? [{ debugName: "bars" }] : /* istanbul ignore next */ []));
8586
8902
  // ── Axes ───────────────────────────────────────────────────────
8587
8903
  gridY = computed(() => {
@@ -8616,34 +8932,49 @@ class StrctChart {
8616
8932
  const ls = this.labels();
8617
8933
  const fmt = this.xFormat();
8618
8934
  const xt = this.xTicks();
8619
- let items;
8620
- if (xt && xt > 1 && ls.length > xt) {
8621
- const stepI = (ls.length - 1) / (xt - 1);
8622
- const idxs = Array.from(new Set(Array.from({ length: xt }, (_, k) => Math.round(k * stepI))));
8623
- items = idxs.map((i) => ({ l: ls[i], i }));
8935
+ const [s, e] = this.domain();
8936
+ const last = Math.min(e, ls.length - 1);
8937
+ let idxs;
8938
+ if (xt && xt > 1 && last - s + 1 > xt) {
8939
+ const stepI = (last - s) / (xt - 1);
8940
+ idxs = Array.from(new Set(Array.from({ length: xt }, (_, k) => s + Math.round(k * stepI))));
8624
8941
  }
8625
8942
  else {
8626
- items = ls.map((l, i) => ({ l, i }));
8627
- }
8628
- return items.map(({ l, i }) => ({ text: fmt ? fmt(l, i) : l, i }));
8943
+ idxs = Array.from({ length: Math.max(0, last - s + 1) }, (_, k) => s + k);
8944
+ }
8945
+ return idxs
8946
+ .filter((i) => i >= 0 && i < ls.length)
8947
+ .map((i) => ({
8948
+ text: fmt ? fmt(ls[i], i) : ls[i],
8949
+ i,
8950
+ }));
8629
8951
  }, ...(ngDevMode ? [{ debugName: "displayLabels" }] : /* istanbul ignore next */ []));
8630
- // ── Hover ──────────────────────────────────────────────────────
8952
+ // ── Hover (driven by dispIdx: local pointer, else external activeIndex) ──
8631
8953
  hoverX = computed(() => {
8632
- const i = this.hoverIdx();
8954
+ const i = this.dispIdx();
8633
8955
  if (i == null)
8634
8956
  return null;
8635
- return this.pl() + i * this.stepX();
8957
+ return this.xOf(i);
8636
8958
  }, ...(ngDevMode ? [{ debugName: "hoverX" }] : /* istanbul ignore next */ []));
8637
8959
  hoverPt = computed(() => {
8638
- const i = this.hoverIdx();
8960
+ const i = this.dispIdx();
8639
8961
  const p = this.points();
8640
8962
  return i != null && i >= 0 && i < p.length ? p[i] : null;
8641
8963
  }, ...(ngDevMode ? [{ debugName: "hoverPt" }] : /* istanbul ignore next */ []));
8642
8964
  hoverValue = computed(() => {
8643
- const i = this.hoverIdx();
8965
+ const i = this.dispIdx();
8644
8966
  const d = this.data();
8645
- return i != null && i >= 0 && i < d.length ? d[i] : '';
8967
+ const v = i != null && i >= 0 && i < d.length ? d[i] : null;
8968
+ return isVal(v) ? v : '';
8646
8969
  }, ...(ngDevMode ? [{ debugName: "hoverValue" }] : /* istanbul ignore next */ []));
8970
+ /** The hovered slot exists but holds a gap (single-series). */
8971
+ hoverGap = computed(() => {
8972
+ const i = this.dispIdx();
8973
+ if (i == null || this.isMulti())
8974
+ return false;
8975
+ const d = this.data();
8976
+ return i >= 0 && i < d.length && !isVal(d[i]);
8977
+ }, ...(ngDevMode ? [{ debugName: "hoverGap" }] : /* istanbul ignore next */ []));
8647
8978
  /** hoverValue run through valueFormat (unit / precision), else the raw value. */
8648
8979
  hoverValueText = computed(() => {
8649
8980
  const v = this.hoverValue();
@@ -8658,20 +8989,24 @@ class StrctChart {
8658
8989
  return f ? f(this.absDelta()) : String(this.absDelta());
8659
8990
  }, ...(ngDevMode ? [{ debugName: "absDeltaText" }] : /* istanbul ignore next */ []));
8660
8991
  hoverLabel = computed(() => {
8661
- const i = this.hoverIdx();
8992
+ const i = this.dispIdx();
8662
8993
  const l = this.labels();
8663
8994
  return i != null && i >= 0 && i < l.length ? l[i] : '';
8664
8995
  }, ...(ngDevMode ? [{ debugName: "hoverLabel" }] : /* istanbul ignore next */ []));
8665
- /** Change from the previous point (single-series; null at the first point). */
8996
+ /** Change from the previous point (single-series; null at the first point or across a gap). */
8666
8997
  hoverDelta = computed(() => {
8667
- const i = this.hoverIdx();
8998
+ const i = this.dispIdx();
8668
8999
  const d = this.data();
8669
- return i != null && i > 0 && i < d.length ? d[i] - d[i - 1] : null;
9000
+ if (i == null || i <= 0 || i >= d.length)
9001
+ return null;
9002
+ const cur = d[i];
9003
+ const prev = d[i - 1];
9004
+ return isVal(cur) && isVal(prev) ? cur - prev : null;
8670
9005
  }, ...(ngDevMode ? [{ debugName: "hoverDelta" }] : /* istanbul ignore next */ []));
8671
9006
  absDelta = computed(() => Math.abs(this.hoverDelta() ?? 0), ...(ngDevMode ? [{ debugName: "absDelta" }] : /* istanbul ignore next */ []));
8672
9007
  /** Second tooltip line: "Xs ago" while live, else the x label. */
8673
9008
  hoverMeta = computed(() => {
8674
- const i = this.hoverIdx();
9009
+ const i = this.dispIdx();
8675
9010
  if (i == null)
8676
9011
  return '';
8677
9012
  if (this.live()) {
@@ -8684,59 +9019,102 @@ class StrctChart {
8684
9019
  const l = this.labels();
8685
9020
  return i >= 0 && i < l.length ? l[i] : '';
8686
9021
  }, ...(ngDevMode ? [{ debugName: "hoverMeta" }] : /* istanbul ignore next */ []));
8687
- /** Per-series value at the hovered index (multi-series tooltip). */
9022
+ /** Per-series value at the hovered index (multi-series tooltip; bands as `avg (min–max)`). */
8688
9023
  hoverRows = computed(() => {
8689
- const i = this.hoverIdx();
9024
+ const i = this.dispIdx();
8690
9025
  const s = this.multiSeries();
8691
9026
  if (i == null || !s.length)
8692
9027
  return [];
8693
9028
  const f = this.valueFormat();
9029
+ const fmt = (v) => (f ? f(v) : String(v));
8694
9030
  return s
8695
9031
  .map((x) => {
8696
9032
  const li = i - x.offset;
8697
- const v = li >= 0 && li < x.data.length ? x.data[li] : null;
9033
+ const raw = li >= 0 && li < x.data.length ? x.data[li] : null;
9034
+ const v = isVal(raw) ? raw : null;
9035
+ const lo = x.lower?.[li];
9036
+ const hi = x.upper?.[li];
9037
+ const band = isVal(lo) && isVal(hi) ? ` (${fmt(lo)}–${fmt(hi)})` : '';
8698
9038
  return {
8699
9039
  label: x.label,
8700
9040
  color: x.color,
8701
9041
  value: v,
8702
- text: v == null ? '' : f ? f(v) : String(v),
9042
+ text: v == null ? '' : fmt(v) + band,
8703
9043
  };
8704
9044
  })
8705
9045
  .filter((r) => r.value !== null);
8706
9046
  }, ...(ngDevMode ? [{ debugName: "hoverRows" }] : /* istanbul ignore next */ []));
8707
9047
  /** Per-series hover dots (multi-series). */
8708
9048
  hoverDots = computed(() => {
8709
- const i = this.hoverIdx();
9049
+ const i = this.dispIdx();
8710
9050
  const s = this.multiSeries();
8711
9051
  if (i == null || !s.length)
8712
9052
  return [];
8713
9053
  return s
8714
9054
  .map((x) => {
8715
9055
  const li = i - x.offset;
8716
- return li >= 0 && li < x.pts.length
8717
- ? { x: x.pts[li].x, y: x.pts[li].y, color: x.color }
8718
- : null;
9056
+ const p = li >= 0 && li < x.pts.length ? x.pts[li] : null;
9057
+ return p ? { x: p.x, y: p.y, color: x.color } : null;
8719
9058
  })
8720
9059
  .filter((d) => d !== null);
8721
9060
  }, ...(ngDevMode ? [{ debugName: "hoverDots" }] : /* istanbul ignore next */ []));
9061
+ // ── Annotations ────────────────────────────────────────────────
9062
+ annotationLines = computed(() => {
9063
+ const [s, e] = this.domain();
9064
+ return this.annotations()
9065
+ .filter((a) => a.index >= s && a.index <= e)
9066
+ .map((a) => ({
9067
+ x: this.xOf(a.index),
9068
+ color: COLOR$1[a.status ?? 'accent'],
9069
+ dashed: a.dashed ?? true,
9070
+ label: a.label ?? '',
9071
+ index: a.index,
9072
+ }));
9073
+ }, ...(ngDevMode ? [{ debugName: "annotationLines" }] : /* istanbul ignore next */ []));
9074
+ /** The annotation sitting exactly under the crosshair, if any. */
9075
+ annAt = computed(() => {
9076
+ const i = this.dispIdx();
9077
+ if (i == null)
9078
+ return null;
9079
+ return this.annotationLines().find((a) => a.index === i && a.label) ?? null;
9080
+ }, ...(ngDevMode ? [{ debugName: "annAt" }] : /* istanbul ignore next */ []));
9081
+ // ── Brush / zoom ───────────────────────────────────────────────
9082
+ brushRect = computed(() => {
9083
+ const d = this.brushDrag();
9084
+ if (!d)
9085
+ return null;
9086
+ const x1 = this.xOf(Math.min(d.a, d.b));
9087
+ const x2 = this.xOf(Math.max(d.a, d.b));
9088
+ return { x: round$1(x1), w: round$1(Math.max(1, x2 - x1)) };
9089
+ }, ...(ngDevMode ? [{ debugName: "brushRect" }] : /* istanbul ignore next */ []));
9090
+ /** Zoom back out to the full data window (also emits `brushChange` null). */
9091
+ resetZoom() {
9092
+ this.brushDrag.set(null);
9093
+ if (this.viewRange() !== null) {
9094
+ this.viewRange.set(null);
9095
+ this.brushChange.emit(null);
9096
+ }
9097
+ }
8722
9098
  /** Screen-reader summary of the whole chart (role="img" name). */
8723
9099
  chartAria = computed(() => {
8724
9100
  const f = this.valueFormat() ?? ((v) => String(Math.round(v * 100) / 100));
8725
9101
  if (this.isMulti()) {
8726
9102
  const parts = this.multiSeries().map((s) => {
8727
- const last = s.data.length ? f(s.data[s.data.length - 1]) : '';
9103
+ const reals = s.data.filter(isVal);
9104
+ const last = reals.length ? f(reals[reals.length - 1]) : '';
8728
9105
  return `${s.label || 'series'} latest ${last}`;
8729
9106
  });
8730
9107
  return `Chart, ${this.multiSeries().length} series: ${parts.join('; ')}`;
8731
9108
  }
8732
9109
  const d = this.data();
8733
- if (!d.length)
9110
+ const reals = d.filter(isVal);
9111
+ if (!reals.length)
8734
9112
  return this.emptyText();
8735
- return `Chart, ${d.length} points. Min ${f(Math.min(...d))}, max ${f(Math.max(...d))}, latest ${f(d[d.length - 1])}`;
9113
+ return `Chart, ${d.length} points. Min ${f(Math.min(...reals))}, max ${f(Math.max(...reals))}, latest ${f(reals[reals.length - 1])}`;
8736
9114
  }, ...(ngDevMode ? [{ debugName: "chartAria" }] : /* istanbul ignore next */ []));
8737
9115
  /** aria-live text announcing the hovered / keyboard-selected point. */
8738
9116
  srText = computed(() => {
8739
- const i = this.hoverIdx();
9117
+ const i = this.dispIdx();
8740
9118
  if (i == null)
8741
9119
  return '';
8742
9120
  if (this.isMulti()) {
@@ -8746,61 +9124,221 @@ class StrctChart {
8746
9124
  return `${this.hoverMeta() || 'point ' + (i + 1)}: ${rows}`;
8747
9125
  }
8748
9126
  const meta = this.hoverMeta();
8749
- return `${meta ? meta + ': ' : ''}${this.hoverValueText()}`;
9127
+ const value = this.hoverGap() ? this.gapText() : this.hoverValueText();
9128
+ return `${meta ? meta + ': ' : ''}${value}`;
8750
9129
  }, ...(ngDevMode ? [{ debugName: "srText" }] : /* istanbul ignore next */ []));
8751
- /** Pixel x of a data index (shared by the plot and the x-axis labels). */
9130
+ /** Pixel x of a data index (shared by the plot, labels and annotations). */
8752
9131
  xOf(i) {
8753
- return this.pl() + i * this.stepX();
9132
+ const [s] = this.domain();
9133
+ return this.pl() + (i - s) * this.stepX();
9134
+ }
9135
+ /** Set the local hover index, emitting `hoverIndex` on change. */
9136
+ setHover(i) {
9137
+ if (i === this.hoverIdx())
9138
+ return;
9139
+ this.hoverIdx.set(i);
9140
+ this.hoverIndex.emit(i);
8754
9141
  }
8755
- /** Keyboard access to the crosshair: arrows walk the points. */
9142
+ onLeave() {
9143
+ this.setHover(null);
9144
+ }
9145
+ /** Keyboard access: arrows walk the points; Escape unwinds brush → zoom → crosshair. */
8756
9146
  onKey(event) {
8757
- if (!this.interactive() || this.type() === 'bar')
9147
+ if (event.key === 'Escape') {
9148
+ if (this.brushDrag()) {
9149
+ this.brushDrag.set(null);
9150
+ }
9151
+ else if (this.zoomed()) {
9152
+ this.resetZoom();
9153
+ }
9154
+ else {
9155
+ this.setHover(null);
9156
+ }
8758
9157
  return;
8759
- const n = this.nx();
8760
- if (!n)
9158
+ }
9159
+ if (!this.interactive() || this.type() === 'bar')
8761
9160
  return;
9161
+ const [s, e] = this.domain();
8762
9162
  const cur = this.hoverIdx();
8763
9163
  let next = null;
8764
9164
  switch (event.key) {
8765
9165
  case 'ArrowRight':
8766
- next = cur == null ? 0 : Math.min(n - 1, cur + 1);
9166
+ next = cur == null ? s : Math.min(e, cur + 1);
8767
9167
  break;
8768
9168
  case 'ArrowLeft':
8769
- next = cur == null ? n - 1 : Math.max(0, cur - 1);
9169
+ next = cur == null ? e : Math.max(s, cur - 1);
8770
9170
  break;
8771
9171
  case 'Home':
8772
- next = 0;
9172
+ next = s;
8773
9173
  break;
8774
9174
  case 'End':
8775
- next = n - 1;
9175
+ next = e;
8776
9176
  break;
8777
- case 'Escape':
8778
- this.hoverIdx.set(null);
8779
- return;
8780
9177
  default:
8781
9178
  return;
8782
9179
  }
8783
9180
  event.preventDefault();
8784
- this.hoverIdx.set(next);
9181
+ this.setHover(next);
8785
9182
  }
8786
- onMove(event) {
8787
- if (!this.interactive() || (this.type() === 'bar' && !this.isMulti()))
8788
- return;
9183
+ /** Data index under the pointer, clamped to the visible window. */
9184
+ idxAt(event) {
8789
9185
  const el = this.svgRef()?.nativeElement;
8790
- const n = this.nx();
8791
9186
  if (!el || this.isEmpty())
8792
- return;
9187
+ return null;
8793
9188
  const rect = el.getBoundingClientRect();
8794
9189
  if (!rect.width)
8795
- return;
9190
+ return null;
8796
9191
  const plFrac = this.pl() / this.width();
8797
9192
  const rFrac = PAD.r / this.width();
8798
9193
  const fx = ((event.clientX - rect.left) / rect.width - plFrac) / (1 - plFrac - rFrac);
8799
- const idx = Math.max(0, Math.min(n - 1, Math.round(fx * (n - 1))));
8800
- this.hoverIdx.set(idx);
9194
+ const [s, e] = this.domain();
9195
+ return Math.max(s, Math.min(e, s + Math.round(fx * (e - s))));
9196
+ }
9197
+ onDown(event) {
9198
+ if (!this.brushEnabled() || this.isEmpty())
9199
+ return;
9200
+ const i = this.idxAt(event);
9201
+ if (i == null)
9202
+ return;
9203
+ event.currentTarget?.setPointerCapture?.(event.pointerId);
9204
+ this.brushDrag.set({ a: i, b: i });
9205
+ this.setHover(null);
9206
+ event.preventDefault();
9207
+ }
9208
+ onMove(event) {
9209
+ const drag = this.brushDrag();
9210
+ if (drag) {
9211
+ const i = this.idxAt(event);
9212
+ if (i != null && i !== drag.b)
9213
+ this.brushDrag.set({ a: drag.a, b: i });
9214
+ return;
9215
+ }
9216
+ if (!this.interactive() || (this.type() === 'bar' && !this.isMulti()))
9217
+ return;
9218
+ const i = this.idxAt(event);
9219
+ if (i != null)
9220
+ this.setHover(i);
9221
+ }
9222
+ onUp() {
9223
+ const d = this.brushDrag();
9224
+ if (!d)
9225
+ return;
9226
+ this.brushDrag.set(null);
9227
+ const lo = Math.min(d.a, d.b);
9228
+ const hi = Math.max(d.a, d.b);
9229
+ if (hi - lo < 1)
9230
+ return; // a click, not a selection
9231
+ if (this.zoom())
9232
+ this.viewRange.set([lo, hi]);
9233
+ this.brushChange.emit([lo, hi]);
9234
+ }
9235
+ onDblClick() {
9236
+ if (this.brushEnabled())
9237
+ this.resetZoom();
9238
+ }
9239
+ // ── Export (FR-CHART-13) ───────────────────────────────────────
9240
+ /**
9241
+ * The rendered chart as a standalone SVG string — theme colors resolved to
9242
+ * literals, background baked in, y-tick / x-label text included.
9243
+ */
9244
+ toSVG() {
9245
+ const svg = this.svgRef()?.nativeElement;
9246
+ if (!svg || typeof getComputedStyle === 'undefined')
9247
+ return '';
9248
+ const doc = svg.ownerDocument;
9249
+ const NS = 'http://www.w3.org/2000/svg';
9250
+ const clone = svg.cloneNode(true);
9251
+ clone.setAttribute('xmlns', NS);
9252
+ clone.removeAttribute('class');
9253
+ clone.removeAttribute('style');
9254
+ clone.removeAttribute('tabindex');
9255
+ const PROPS = [
9256
+ 'stroke',
9257
+ 'fill',
9258
+ 'stroke-width',
9259
+ 'stroke-dasharray',
9260
+ 'stroke-linecap',
9261
+ 'stroke-linejoin',
9262
+ 'opacity',
9263
+ 'fill-opacity',
9264
+ 'stroke-opacity',
9265
+ ];
9266
+ const src = Array.from(svg.querySelectorAll('*'));
9267
+ const dst = Array.from(clone.querySelectorAll('*'));
9268
+ src.forEach((el, i) => {
9269
+ const cs = getComputedStyle(el);
9270
+ for (const p of PROPS) {
9271
+ const v = cs.getPropertyValue(p);
9272
+ if (v)
9273
+ dst[i].setAttribute(p, v);
9274
+ }
9275
+ dst[i].removeAttribute('class');
9276
+ });
9277
+ const rootCs = getComputedStyle(svg);
9278
+ const bg = rootCs.getPropertyValue('--bg-1').trim();
9279
+ const fg = rootCs.getPropertyValue('--t3').trim() || '#888';
9280
+ const hasLabels = this.displayLabels().length > 0;
9281
+ const w = this.width();
9282
+ const h = this.height() + (hasLabels ? 18 : 0);
9283
+ clone.setAttribute('viewBox', `0 0 ${w} ${h}`);
9284
+ clone.setAttribute('width', String(w));
9285
+ clone.setAttribute('height', String(h));
9286
+ if (bg) {
9287
+ const rect = doc.createElementNS(NS, 'rect');
9288
+ rect.setAttribute('width', String(w));
9289
+ rect.setAttribute('height', String(h));
9290
+ rect.setAttribute('fill', bg);
9291
+ clone.insertBefore(rect, clone.firstChild);
9292
+ }
9293
+ const text = (x, y, content, anchor) => {
9294
+ const t = doc.createElementNS(NS, 'text');
9295
+ t.setAttribute('x', String(round$1(x)));
9296
+ t.setAttribute('y', String(round$1(y)));
9297
+ t.setAttribute('fill', fg);
9298
+ t.setAttribute('font-size', '10');
9299
+ t.setAttribute('font-family', 'monospace');
9300
+ t.setAttribute('text-anchor', anchor);
9301
+ t.textContent = content;
9302
+ clone.appendChild(t);
9303
+ };
9304
+ for (const tick of this.yAxisTicks())
9305
+ text(this.pl() - 6, tick.y + 3.5, tick.text, 'end');
9306
+ if (hasLabels) {
9307
+ for (const l of this.displayLabels())
9308
+ text(this.xOf(l.i), this.height() + 12, l.text, 'middle');
9309
+ }
9310
+ return new XMLSerializer().serializeToString(clone);
9311
+ }
9312
+ /** The chart as a PNG data URL at the given scale (default 2×). */
9313
+ toPNG(scale = 2) {
9314
+ const s = this.toSVG();
9315
+ const w = this.width();
9316
+ const h = this.height() + (this.displayLabels().length ? 18 : 0);
9317
+ return new Promise((resolve, reject) => {
9318
+ if (!s) {
9319
+ reject(new Error('chart is not rendered'));
9320
+ return;
9321
+ }
9322
+ const img = new Image();
9323
+ img.onload = () => {
9324
+ const canvas = document.createElement('canvas');
9325
+ canvas.width = Math.round(w * scale);
9326
+ canvas.height = Math.round(h * scale);
9327
+ const ctx = canvas.getContext('2d');
9328
+ if (!ctx) {
9329
+ reject(new Error('no 2d canvas context'));
9330
+ return;
9331
+ }
9332
+ ctx.scale(scale, scale);
9333
+ ctx.drawImage(img, 0, 0);
9334
+ resolve(canvas.toDataURL('image/png'));
9335
+ };
9336
+ img.onerror = () => reject(new Error('SVG rasterization failed'));
9337
+ img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(s);
9338
+ });
8801
9339
  }
8802
9340
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
8803
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctChart, isStandalone: true, selector: "strct-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, curve: { classPropertyName: "curve", publicName: "curve", isSignal: true, isRequired: false, transformFunction: null }, area: { classPropertyName: "area", publicName: "area", isSignal: true, isRequired: false, transformFunction: null }, glow: { classPropertyName: "glow", publicName: "glow", isSignal: true, isRequired: false, transformFunction: null }, live: { classPropertyName: "live", publicName: "live", isSignal: true, isRequired: false, transformFunction: null }, interval: { classPropertyName: "interval", publicName: "interval", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, dots: { classPropertyName: "dots", publicName: "dots", isSignal: true, isRequired: false, transformFunction: null }, legend: { classPropertyName: "legend", publicName: "legend", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, xTicks: { classPropertyName: "xTicks", publicName: "xTicks", isSignal: true, isRequired: false, transformFunction: null }, xFormat: { classPropertyName: "xFormat", publicName: "xFormat", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, yAxis: { classPropertyName: "yAxis", publicName: "yAxis", isSignal: true, isRequired: false, transformFunction: null }, yTicks: { classPropertyName: "yTicks", publicName: "yTicks", isSignal: true, isRequired: false, transformFunction: null }, axisFormat: { classPropertyName: "axisFormat", publicName: "axisFormat", isSignal: true, isRequired: false, transformFunction: null }, thresholds: { classPropertyName: "thresholds", publicName: "thresholds", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, agoFormat: { classPropertyName: "agoFormat", publicName: "agoFormat", isSignal: true, isRequired: false, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-chart--glow": "glow()", "style.--strct-chart-c": "color()" }, classAttribute: "strct-chart" }, viewQueries: [{ propertyName: "svgRef", first: true, predicate: ["svg"], descendants: true, isSignal: true }], ngImport: i0, template: `
9341
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctChart, isStandalone: true, selector: "strct-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, curve: { classPropertyName: "curve", publicName: "curve", isSignal: true, isRequired: false, transformFunction: null }, area: { classPropertyName: "area", publicName: "area", isSignal: true, isRequired: false, transformFunction: null }, glow: { classPropertyName: "glow", publicName: "glow", isSignal: true, isRequired: false, transformFunction: null }, live: { classPropertyName: "live", publicName: "live", isSignal: true, isRequired: false, transformFunction: null }, interval: { classPropertyName: "interval", publicName: "interval", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, dots: { classPropertyName: "dots", publicName: "dots", isSignal: true, isRequired: false, transformFunction: null }, legend: { classPropertyName: "legend", publicName: "legend", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, xTicks: { classPropertyName: "xTicks", publicName: "xTicks", isSignal: true, isRequired: false, transformFunction: null }, xFormat: { classPropertyName: "xFormat", publicName: "xFormat", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, yAxis: { classPropertyName: "yAxis", publicName: "yAxis", isSignal: true, isRequired: false, transformFunction: null }, yTicks: { classPropertyName: "yTicks", publicName: "yTicks", isSignal: true, isRequired: false, transformFunction: null }, axisFormat: { classPropertyName: "axisFormat", publicName: "axisFormat", isSignal: true, isRequired: false, transformFunction: null }, thresholds: { classPropertyName: "thresholds", publicName: "thresholds", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, agoFormat: { classPropertyName: "agoFormat", publicName: "agoFormat", isSignal: true, isRequired: false, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null }, annotations: { classPropertyName: "annotations", publicName: "annotations", isSignal: true, isRequired: false, transformFunction: null }, activeIndex: { classPropertyName: "activeIndex", publicName: "activeIndex", isSignal: true, isRequired: false, transformFunction: null }, brush: { classPropertyName: "brush", publicName: "brush", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null }, gapText: { classPropertyName: "gapText", publicName: "gapText", isSignal: true, isRequired: false, transformFunction: null }, resetLabel: { classPropertyName: "resetLabel", publicName: "resetLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hoverIndex: "hoverIndex", brushChange: "brushChange" }, host: { properties: { "class.strct-chart--glow": "glow()", "class.strct-chart--brush": "brush() || zoom()", "style.--strct-chart-c": "color()" }, classAttribute: "strct-chart" }, viewQueries: [{ propertyName: "svgRef", first: true, predicate: ["svg"], descendants: true, isSignal: true }], ngImport: i0, template: `
8804
9342
  @if (isEmpty()) {
8805
9343
  <div class="strct-chart__empty" [style.height.px]="height()">{{ emptyText() }}</div>
8806
9344
  } @else {
@@ -8835,10 +9373,14 @@ class StrctChart {
8835
9373
  [attr.width]="width()"
8836
9374
  [attr.height]="height()"
8837
9375
  [style.height.px]="height()"
9376
+ (pointerdown)="onDown($event)"
8838
9377
  (pointermove)="onMove($event)"
8839
- (pointerleave)="hoverIdx.set(null)"
9378
+ (pointerup)="onUp()"
9379
+ (lostpointercapture)="onUp()"
9380
+ (pointerleave)="onLeave()"
9381
+ (dblclick)="onDblClick()"
8840
9382
  (keydown)="onKey($event)"
8841
- (blur)="hoverIdx.set(null)"
9383
+ (blur)="onLeave()"
8842
9384
  >
8843
9385
  <defs>
8844
9386
  <linearGradient [attr.id]="gradId" x1="0" y1="0" x2="0" y2="1">
@@ -8862,6 +9404,19 @@ class StrctChart {
8862
9404
  }
8863
9405
  }
8864
9406
 
9407
+ <!-- Event / annotation markers: behind the data, above the grid. -->
9408
+ @for (a of annotationLines(); track $index) {
9409
+ <line
9410
+ class="strct-chart__ann"
9411
+ [class.strct-chart__ann--dashed]="a.dashed"
9412
+ [attr.x1]="a.x"
9413
+ [attr.x2]="a.x"
9414
+ [attr.y1]="pad.t"
9415
+ [attr.y2]="height() - pad.b"
9416
+ [attr.stroke]="a.color"
9417
+ />
9418
+ }
9419
+
8865
9420
  @if (type() === 'bar' && !isMulti()) {
8866
9421
  @for (b of bars(); track $index) {
8867
9422
  <rect
@@ -8882,6 +9437,9 @@ class StrctChart {
8882
9437
  >
8883
9438
  @if (isMulti()) {
8884
9439
  @for (s of multiSeries(); track $index) {
9440
+ @if (s.bandPath) {
9441
+ <path class="strct-chart__band" [attr.d]="s.bandPath" [attr.fill]="s.color" />
9442
+ }
8885
9443
  @if (s.area && s.areaPath) {
8886
9444
  <path
8887
9445
  class="strct-chart__area strct-chart__area--flat"
@@ -8916,7 +9474,7 @@ class StrctChart {
8916
9474
  [attr.stroke]="color()"
8917
9475
  />
8918
9476
  @if (dots()) {
8919
- @for (p of points(); track $index) {
9477
+ @for (p of dotPts(); track $index) {
8920
9478
  <circle
8921
9479
  class="strct-chart__dot"
8922
9480
  [attr.cx]="p.x"
@@ -8942,6 +9500,16 @@ class StrctChart {
8942
9500
  />
8943
9501
  }
8944
9502
 
9503
+ @if (brushRect(); as br) {
9504
+ <rect
9505
+ class="strct-chart__brush"
9506
+ [attr.x]="br.x"
9507
+ [attr.y]="pad.t"
9508
+ [attr.width]="br.w"
9509
+ [attr.height]="height() - pad.t - pad.b"
9510
+ />
9511
+ }
9512
+
8945
9513
  @if (interactive() && hoverX() !== null) {
8946
9514
  <line
8947
9515
  class="strct-chart__cross"
@@ -9002,12 +9570,29 @@ class StrctChart {
9002
9570
  }
9003
9571
  }
9004
9572
 
9573
+ @for (a of annotationLines(); track $index) {
9574
+ @if (a.label) {
9575
+ <div class="strct-chart__ann-label" [style.left.px]="a.x" [style.color]="a.color">
9576
+ {{ a.label }}
9577
+ </div>
9578
+ }
9579
+ }
9580
+
9581
+ @if (zoomed()) {
9582
+ <button type="button" class="strct-chart__reset" (click)="resetZoom()">
9583
+ ⟲ {{ resetLabel() }}
9584
+ </button>
9585
+ }
9586
+
9005
9587
  @if (interactive() && hoverX() !== null) {
9006
9588
  @if (isMulti()) {
9007
9589
  <div class="strct-chart__tip strct-chart__tip--multi" [style.left.px]="hoverX()">
9008
9590
  @if (hoverMeta()) {
9009
9591
  <span class="strct-chart__tip-l">{{ hoverMeta() }}</span>
9010
9592
  }
9593
+ @if (annAt(); as ann) {
9594
+ <span class="strct-chart__tip-ann" [style.color]="ann.color">{{ ann.label }}</span>
9595
+ }
9011
9596
  @for (r of hoverRows(); track r.label) {
9012
9597
  <span class="strct-chart__tip-row">
9013
9598
  <span class="strct-chart__tip-sw" [style.background]="r.color"></span>
@@ -9039,6 +9624,17 @@ class StrctChart {
9039
9624
  }
9040
9625
  </span>
9041
9626
  }
9627
+ @if (annAt(); as ann) {
9628
+ <span class="strct-chart__tip-ann" [style.color]="ann.color">{{ ann.label }}</span>
9629
+ }
9630
+ </div>
9631
+ } @else if (hoverGap()) {
9632
+ <!-- A gap point: keep the time slot, say "no data" instead of a value. -->
9633
+ <div class="strct-chart__tip strct-chart__tip--gap" [style.left.px]="hoverX()">
9634
+ <span class="strct-chart__tip-v">{{ gapText() }}</span>
9635
+ @if (hoverMeta()) {
9636
+ <span class="strct-chart__tip-l">{{ hoverMeta() }}</span>
9637
+ }
9042
9638
  </div>
9043
9639
  }
9044
9640
  }
@@ -9053,7 +9649,7 @@ class StrctChart {
9053
9649
  <div class="strct-chart__labels">
9054
9650
  @for (l of displayLabels(); track $index) {
9055
9651
  <span
9056
- [class.strct-chart__label--active]="interactive() && hoverIdx() === l.i"
9652
+ [class.strct-chart__label--active]="interactive() && dispIdx() === l.i"
9057
9653
  [style.left.px]="xOf(l.i)"
9058
9654
  >{{ l.text }}</span
9059
9655
  >
@@ -9061,7 +9657,7 @@ class StrctChart {
9061
9657
  </div>
9062
9658
  }
9063
9659
  }
9064
- `, isInline: true, styles: [".strct-chart{display:block;position:relative}.strct-chart__plot{position:relative}.strct-chart__svg{width:100%;display:block;touch-action:none}.strct-chart__svg:focus-visible{outline:2px solid var(--acc50);outline-offset:2px;border-radius:var(--radius-sm)}.strct-chart__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-chart__empty{display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--t3)}.strct-chart__grid{stroke:var(--b1);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__line{vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round}.strct-chart__area{stroke:none}.strct-chart__area--flat{opacity:.14}.strct-chart__dot,.strct-chart__hoverdot,.strct-chart__head-dot{stroke:var(--bg-1);stroke-width:1.5}.strct-chart__cross{stroke:var(--strct-chart-c);stroke-width:1;opacity:.4;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}.strct-chart__threshold{stroke-width:1;opacity:.85;vector-effect:non-scaling-stroke}.strct-chart__threshold--dashed{stroke-dasharray:4 3}.strct-chart__bar{rx:1.5}.strct-chart--glow .strct-chart__line{filter:drop-shadow(0 0 1.5px var(--strct-chart-c)) drop-shadow(0 0 5px var(--strct-chart-c))}.strct-chart--glow .strct-chart__head-dot{filter:drop-shadow(0 0 2px var(--strct-chart-c)) drop-shadow(0 0 6px var(--strct-chart-c))}.strct-chart--glow .strct-chart__hoverdot{filter:drop-shadow(0 0 4px var(--strct-chart-c))}.strct-chart__ytick{position:absolute;left:0;width:34px;text-align:end;transform:translateY(-50%);pointer-events:none;font-family:var(--mono);font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__thr{position:absolute;right:2px;transform:translateY(-50%);pointer-events:none;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;background:var(--bg-1);padding:0 3px;border-radius:3px}.strct-chart__axis-y{position:absolute;left:0;transform:translateY(-50%);pointer-events:none;padding:1px 5px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);font-family:var(--mono);font-size:12px;font-weight:600;color:var(--t2);font-variant-numeric:tabular-nums;z-index:2}.strct-chart__label--active{color:var(--t1);font-weight:700}.strct-chart__legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-bottom:8px;font-size:12px;color:var(--t2)}.strct-chart__leg{display:inline-flex;align-items:center;gap:6px}.strct-chart__leg-sw{width:9px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip{position:absolute;transform:translate(-50%,calc(-100% - 10px));pointer-events:none;display:flex;flex-direction:column;align-items:center;gap:1px;padding:4px 8px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);box-shadow:var(--shadow-elevated);white-space:nowrap;z-index:2}.strct-chart__tip--multi{top:6px;transform:translate(-50%);align-items:stretch;gap:3px}.strct-chart__tip-v{font-size:12px;font-weight:700;color:var(--t1);font-variant-numeric:tabular-nums}.strct-chart__tip-meta{display:inline-flex;align-items:center;gap:5px}.strct-chart__tip-delta{font-size:12px;font-weight:600;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__tip-delta--up{color:var(--success)}.strct-chart__tip-delta--down{color:var(--critical)}.strct-chart__tip-l{font-size:12px;color:var(--t3)}.strct-chart__tip-row{display:inline-flex;align-items:center;gap:6px;font-size:12px}.strct-chart__tip-sw{width:8px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip-rl{color:var(--t3);margin-inline-end:auto}.strct-chart__tip-rv{color:var(--t1);font-weight:700;font-variant-numeric:tabular-nums}.strct-chart__labels{position:relative;height:15px;margin-top:6px;font-size:12px;color:var(--t3)}.strct-chart__labels span{position:absolute;transform:translate(-50%);white-space:nowrap}@media(prefers-reduced-motion:no-preference){.strct-chart__line--draw{stroke-dasharray:1;stroke-dashoffset:1;animation:strct-chart-draw .9s ease forwards}.strct-chart__pulse{transform-box:fill-box;transform-origin:center;animation:strct-chart-pulse 2.4s ease-out infinite}}@keyframes strct-chart-draw{to{stroke-dashoffset:0}}@keyframes strct-chart-pulse{0%{transform:scale(1);opacity:.45}70%{opacity:0}to{transform:scale(2.5);opacity:0}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9660
+ `, isInline: true, styles: [".strct-chart{display:block;position:relative}.strct-chart__plot{position:relative}.strct-chart__svg{width:100%;display:block;touch-action:none}.strct-chart__svg:focus-visible{outline:2px solid var(--acc50);outline-offset:2px;border-radius:var(--radius-sm)}.strct-chart__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-chart__empty{display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--t3)}.strct-chart__grid{stroke:var(--b1);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__line{vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round}.strct-chart__area{stroke:none}.strct-chart__area--flat{opacity:.14}.strct-chart__dot,.strct-chart__hoverdot,.strct-chart__head-dot{stroke:var(--bg-1);stroke-width:1.5}.strct-chart__cross{stroke:var(--strct-chart-c);stroke-width:1;opacity:.4;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}.strct-chart__threshold{stroke-width:1;opacity:.85;vector-effect:non-scaling-stroke}.strct-chart__threshold--dashed{stroke-dasharray:4 3}.strct-chart__bar{rx:1.5}.strct-chart__band{opacity:.13;stroke:none}.strct-chart__ann{stroke-width:1;opacity:.8;vector-effect:non-scaling-stroke}.strct-chart__ann--dashed{stroke-dasharray:4 3}.strct-chart__ann-label{position:absolute;top:0;transform:translate(-50%);pointer-events:none;font-size:12px;font-weight:600;background:var(--bg-1);padding:0 3px;border-radius:3px;white-space:nowrap}.strct-chart--brush .strct-chart__svg{cursor:crosshair}.strct-chart__brush{fill:var(--acc);opacity:.16;stroke:var(--acc50);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__reset{position:absolute;top:6px;right:8px;z-index:3;display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border:1px solid var(--b2);border-radius:99px;background:var(--bg-a);color:var(--t2);font-family:var(--font);font-size:12px;font-weight:600;cursor:pointer}.strct-chart__reset:hover{color:var(--t1);border-color:var(--acc50)}.strct-chart__reset:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-chart__tip--gap{top:6px;transform:translate(-50%)}.strct-chart__tip-ann{font-size:12px;font-weight:600}.strct-chart--glow .strct-chart__line{filter:drop-shadow(0 0 1.5px var(--strct-chart-c)) drop-shadow(0 0 5px var(--strct-chart-c))}.strct-chart--glow .strct-chart__head-dot{filter:drop-shadow(0 0 2px var(--strct-chart-c)) drop-shadow(0 0 6px var(--strct-chart-c))}.strct-chart--glow .strct-chart__hoverdot{filter:drop-shadow(0 0 4px var(--strct-chart-c))}.strct-chart__ytick{position:absolute;left:0;width:34px;text-align:end;transform:translateY(-50%);pointer-events:none;font-family:var(--mono);font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__thr{position:absolute;right:2px;transform:translateY(-50%);pointer-events:none;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;background:var(--bg-1);padding:0 3px;border-radius:3px}.strct-chart__axis-y{position:absolute;left:0;transform:translateY(-50%);pointer-events:none;padding:1px 5px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);font-family:var(--mono);font-size:12px;font-weight:600;color:var(--t2);font-variant-numeric:tabular-nums;z-index:2}.strct-chart__label--active{color:var(--t1);font-weight:700}.strct-chart__legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-bottom:8px;font-size:12px;color:var(--t2)}.strct-chart__leg{display:inline-flex;align-items:center;gap:6px}.strct-chart__leg-sw{width:9px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip{position:absolute;transform:translate(-50%,calc(-100% - 10px));pointer-events:none;display:flex;flex-direction:column;align-items:center;gap:1px;padding:4px 8px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);box-shadow:var(--shadow-elevated);white-space:nowrap;z-index:2}.strct-chart__tip--multi{top:6px;transform:translate(-50%);align-items:stretch;gap:3px}.strct-chart__tip-v{font-size:12px;font-weight:700;color:var(--t1);font-variant-numeric:tabular-nums}.strct-chart__tip-meta{display:inline-flex;align-items:center;gap:5px}.strct-chart__tip-delta{font-size:12px;font-weight:600;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__tip-delta--up{color:var(--success)}.strct-chart__tip-delta--down{color:var(--critical)}.strct-chart__tip-l{font-size:12px;color:var(--t3)}.strct-chart__tip-row{display:inline-flex;align-items:center;gap:6px;font-size:12px}.strct-chart__tip-sw{width:8px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip-rl{color:var(--t3);margin-inline-end:auto}.strct-chart__tip-rv{color:var(--t1);font-weight:700;font-variant-numeric:tabular-nums}.strct-chart__labels{position:relative;height:15px;margin-top:6px;font-size:12px;color:var(--t3)}.strct-chart__labels span{position:absolute;transform:translate(-50%);white-space:nowrap}@media(prefers-reduced-motion:no-preference){.strct-chart__line--draw{stroke-dasharray:1;stroke-dashoffset:1;animation:strct-chart-draw .9s ease forwards}.strct-chart__pulse{transform-box:fill-box;transform-origin:center;animation:strct-chart-pulse 2.4s ease-out infinite}}@keyframes strct-chart-draw{to{stroke-dashoffset:0}}@keyframes strct-chart-pulse{0%{transform:scale(1);opacity:.45}70%{opacity:0}to{transform:scale(2.5);opacity:0}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9065
9661
  }
9066
9662
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctChart, decorators: [{
9067
9663
  type: Component,
@@ -9100,10 +9696,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9100
9696
  [attr.width]="width()"
9101
9697
  [attr.height]="height()"
9102
9698
  [style.height.px]="height()"
9699
+ (pointerdown)="onDown($event)"
9103
9700
  (pointermove)="onMove($event)"
9104
- (pointerleave)="hoverIdx.set(null)"
9701
+ (pointerup)="onUp()"
9702
+ (lostpointercapture)="onUp()"
9703
+ (pointerleave)="onLeave()"
9704
+ (dblclick)="onDblClick()"
9105
9705
  (keydown)="onKey($event)"
9106
- (blur)="hoverIdx.set(null)"
9706
+ (blur)="onLeave()"
9107
9707
  >
9108
9708
  <defs>
9109
9709
  <linearGradient [attr.id]="gradId" x1="0" y1="0" x2="0" y2="1">
@@ -9127,6 +9727,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9127
9727
  }
9128
9728
  }
9129
9729
 
9730
+ <!-- Event / annotation markers: behind the data, above the grid. -->
9731
+ @for (a of annotationLines(); track $index) {
9732
+ <line
9733
+ class="strct-chart__ann"
9734
+ [class.strct-chart__ann--dashed]="a.dashed"
9735
+ [attr.x1]="a.x"
9736
+ [attr.x2]="a.x"
9737
+ [attr.y1]="pad.t"
9738
+ [attr.y2]="height() - pad.b"
9739
+ [attr.stroke]="a.color"
9740
+ />
9741
+ }
9742
+
9130
9743
  @if (type() === 'bar' && !isMulti()) {
9131
9744
  @for (b of bars(); track $index) {
9132
9745
  <rect
@@ -9147,6 +9760,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9147
9760
  >
9148
9761
  @if (isMulti()) {
9149
9762
  @for (s of multiSeries(); track $index) {
9763
+ @if (s.bandPath) {
9764
+ <path class="strct-chart__band" [attr.d]="s.bandPath" [attr.fill]="s.color" />
9765
+ }
9150
9766
  @if (s.area && s.areaPath) {
9151
9767
  <path
9152
9768
  class="strct-chart__area strct-chart__area--flat"
@@ -9181,7 +9797,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9181
9797
  [attr.stroke]="color()"
9182
9798
  />
9183
9799
  @if (dots()) {
9184
- @for (p of points(); track $index) {
9800
+ @for (p of dotPts(); track $index) {
9185
9801
  <circle
9186
9802
  class="strct-chart__dot"
9187
9803
  [attr.cx]="p.x"
@@ -9207,6 +9823,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9207
9823
  />
9208
9824
  }
9209
9825
 
9826
+ @if (brushRect(); as br) {
9827
+ <rect
9828
+ class="strct-chart__brush"
9829
+ [attr.x]="br.x"
9830
+ [attr.y]="pad.t"
9831
+ [attr.width]="br.w"
9832
+ [attr.height]="height() - pad.t - pad.b"
9833
+ />
9834
+ }
9835
+
9210
9836
  @if (interactive() && hoverX() !== null) {
9211
9837
  <line
9212
9838
  class="strct-chart__cross"
@@ -9267,12 +9893,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9267
9893
  }
9268
9894
  }
9269
9895
 
9896
+ @for (a of annotationLines(); track $index) {
9897
+ @if (a.label) {
9898
+ <div class="strct-chart__ann-label" [style.left.px]="a.x" [style.color]="a.color">
9899
+ {{ a.label }}
9900
+ </div>
9901
+ }
9902
+ }
9903
+
9904
+ @if (zoomed()) {
9905
+ <button type="button" class="strct-chart__reset" (click)="resetZoom()">
9906
+ ⟲ {{ resetLabel() }}
9907
+ </button>
9908
+ }
9909
+
9270
9910
  @if (interactive() && hoverX() !== null) {
9271
9911
  @if (isMulti()) {
9272
9912
  <div class="strct-chart__tip strct-chart__tip--multi" [style.left.px]="hoverX()">
9273
9913
  @if (hoverMeta()) {
9274
9914
  <span class="strct-chart__tip-l">{{ hoverMeta() }}</span>
9275
9915
  }
9916
+ @if (annAt(); as ann) {
9917
+ <span class="strct-chart__tip-ann" [style.color]="ann.color">{{ ann.label }}</span>
9918
+ }
9276
9919
  @for (r of hoverRows(); track r.label) {
9277
9920
  <span class="strct-chart__tip-row">
9278
9921
  <span class="strct-chart__tip-sw" [style.background]="r.color"></span>
@@ -9304,6 +9947,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9304
9947
  }
9305
9948
  </span>
9306
9949
  }
9950
+ @if (annAt(); as ann) {
9951
+ <span class="strct-chart__tip-ann" [style.color]="ann.color">{{ ann.label }}</span>
9952
+ }
9953
+ </div>
9954
+ } @else if (hoverGap()) {
9955
+ <!-- A gap point: keep the time slot, say "no data" instead of a value. -->
9956
+ <div class="strct-chart__tip strct-chart__tip--gap" [style.left.px]="hoverX()">
9957
+ <span class="strct-chart__tip-v">{{ gapText() }}</span>
9958
+ @if (hoverMeta()) {
9959
+ <span class="strct-chart__tip-l">{{ hoverMeta() }}</span>
9960
+ }
9307
9961
  </div>
9308
9962
  }
9309
9963
  }
@@ -9318,7 +9972,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9318
9972
  <div class="strct-chart__labels">
9319
9973
  @for (l of displayLabels(); track $index) {
9320
9974
  <span
9321
- [class.strct-chart__label--active]="interactive() && hoverIdx() === l.i"
9975
+ [class.strct-chart__label--active]="interactive() && dispIdx() === l.i"
9322
9976
  [style.left.px]="xOf(l.i)"
9323
9977
  >{{ l.text }}</span
9324
9978
  >
@@ -9329,9 +9983,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
9329
9983
  `, host: {
9330
9984
  class: 'strct-chart',
9331
9985
  '[class.strct-chart--glow]': 'glow()',
9986
+ '[class.strct-chart--brush]': 'brush() || zoom()',
9332
9987
  '[style.--strct-chart-c]': 'color()',
9333
- }, styles: [".strct-chart{display:block;position:relative}.strct-chart__plot{position:relative}.strct-chart__svg{width:100%;display:block;touch-action:none}.strct-chart__svg:focus-visible{outline:2px solid var(--acc50);outline-offset:2px;border-radius:var(--radius-sm)}.strct-chart__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-chart__empty{display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--t3)}.strct-chart__grid{stroke:var(--b1);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__line{vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round}.strct-chart__area{stroke:none}.strct-chart__area--flat{opacity:.14}.strct-chart__dot,.strct-chart__hoverdot,.strct-chart__head-dot{stroke:var(--bg-1);stroke-width:1.5}.strct-chart__cross{stroke:var(--strct-chart-c);stroke-width:1;opacity:.4;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}.strct-chart__threshold{stroke-width:1;opacity:.85;vector-effect:non-scaling-stroke}.strct-chart__threshold--dashed{stroke-dasharray:4 3}.strct-chart__bar{rx:1.5}.strct-chart--glow .strct-chart__line{filter:drop-shadow(0 0 1.5px var(--strct-chart-c)) drop-shadow(0 0 5px var(--strct-chart-c))}.strct-chart--glow .strct-chart__head-dot{filter:drop-shadow(0 0 2px var(--strct-chart-c)) drop-shadow(0 0 6px var(--strct-chart-c))}.strct-chart--glow .strct-chart__hoverdot{filter:drop-shadow(0 0 4px var(--strct-chart-c))}.strct-chart__ytick{position:absolute;left:0;width:34px;text-align:end;transform:translateY(-50%);pointer-events:none;font-family:var(--mono);font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__thr{position:absolute;right:2px;transform:translateY(-50%);pointer-events:none;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;background:var(--bg-1);padding:0 3px;border-radius:3px}.strct-chart__axis-y{position:absolute;left:0;transform:translateY(-50%);pointer-events:none;padding:1px 5px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);font-family:var(--mono);font-size:12px;font-weight:600;color:var(--t2);font-variant-numeric:tabular-nums;z-index:2}.strct-chart__label--active{color:var(--t1);font-weight:700}.strct-chart__legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-bottom:8px;font-size:12px;color:var(--t2)}.strct-chart__leg{display:inline-flex;align-items:center;gap:6px}.strct-chart__leg-sw{width:9px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip{position:absolute;transform:translate(-50%,calc(-100% - 10px));pointer-events:none;display:flex;flex-direction:column;align-items:center;gap:1px;padding:4px 8px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);box-shadow:var(--shadow-elevated);white-space:nowrap;z-index:2}.strct-chart__tip--multi{top:6px;transform:translate(-50%);align-items:stretch;gap:3px}.strct-chart__tip-v{font-size:12px;font-weight:700;color:var(--t1);font-variant-numeric:tabular-nums}.strct-chart__tip-meta{display:inline-flex;align-items:center;gap:5px}.strct-chart__tip-delta{font-size:12px;font-weight:600;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__tip-delta--up{color:var(--success)}.strct-chart__tip-delta--down{color:var(--critical)}.strct-chart__tip-l{font-size:12px;color:var(--t3)}.strct-chart__tip-row{display:inline-flex;align-items:center;gap:6px;font-size:12px}.strct-chart__tip-sw{width:8px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip-rl{color:var(--t3);margin-inline-end:auto}.strct-chart__tip-rv{color:var(--t1);font-weight:700;font-variant-numeric:tabular-nums}.strct-chart__labels{position:relative;height:15px;margin-top:6px;font-size:12px;color:var(--t3)}.strct-chart__labels span{position:absolute;transform:translate(-50%);white-space:nowrap}@media(prefers-reduced-motion:no-preference){.strct-chart__line--draw{stroke-dasharray:1;stroke-dashoffset:1;animation:strct-chart-draw .9s ease forwards}.strct-chart__pulse{transform-box:fill-box;transform-origin:center;animation:strct-chart-pulse 2.4s ease-out infinite}}@keyframes strct-chart-draw{to{stroke-dashoffset:0}}@keyframes strct-chart-pulse{0%{transform:scale(1);opacity:.45}70%{opacity:0}to{transform:scale(2.5);opacity:0}}\n"] }]
9334
- }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], series: [{ type: i0.Input, args: [{ isSignal: true, alias: "series", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], curve: [{ type: i0.Input, args: [{ isSignal: true, alias: "curve", required: false }] }], area: [{ type: i0.Input, args: [{ isSignal: true, alias: "area", required: false }] }], glow: [{ type: i0.Input, args: [{ isSignal: true, alias: "glow", required: false }] }], live: [{ type: i0.Input, args: [{ isSignal: true, alias: "live", required: false }] }], interval: [{ type: i0.Input, args: [{ isSignal: true, alias: "interval", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], dots: [{ type: i0.Input, args: [{ isSignal: true, alias: "dots", required: false }] }], legend: [{ type: i0.Input, args: [{ isSignal: true, alias: "legend", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], xTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "xTicks", required: false }] }], xFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "xFormat", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], yAxis: [{ type: i0.Input, args: [{ isSignal: true, alias: "yAxis", required: false }] }], yTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "yTicks", required: false }] }], axisFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisFormat", required: false }] }], thresholds: [{ type: i0.Input, args: [{ isSignal: true, alias: "thresholds", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], agoFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "agoFormat", required: false }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], svgRef: [{ type: i0.ViewChild, args: ['svg', { isSignal: true }] }] } });
9988
+ }, styles: [".strct-chart{display:block;position:relative}.strct-chart__plot{position:relative}.strct-chart__svg{width:100%;display:block;touch-action:none}.strct-chart__svg:focus-visible{outline:2px solid var(--acc50);outline-offset:2px;border-radius:var(--radius-sm)}.strct-chart__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-chart__empty{display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--t3)}.strct-chart__grid{stroke:var(--b1);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__line{vector-effect:non-scaling-stroke;stroke-linejoin:round;stroke-linecap:round}.strct-chart__area{stroke:none}.strct-chart__area--flat{opacity:.14}.strct-chart__dot,.strct-chart__hoverdot,.strct-chart__head-dot{stroke:var(--bg-1);stroke-width:1.5}.strct-chart__cross{stroke:var(--strct-chart-c);stroke-width:1;opacity:.4;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}.strct-chart__threshold{stroke-width:1;opacity:.85;vector-effect:non-scaling-stroke}.strct-chart__threshold--dashed{stroke-dasharray:4 3}.strct-chart__bar{rx:1.5}.strct-chart__band{opacity:.13;stroke:none}.strct-chart__ann{stroke-width:1;opacity:.8;vector-effect:non-scaling-stroke}.strct-chart__ann--dashed{stroke-dasharray:4 3}.strct-chart__ann-label{position:absolute;top:0;transform:translate(-50%);pointer-events:none;font-size:12px;font-weight:600;background:var(--bg-1);padding:0 3px;border-radius:3px;white-space:nowrap}.strct-chart--brush .strct-chart__svg{cursor:crosshair}.strct-chart__brush{fill:var(--acc);opacity:.16;stroke:var(--acc50);stroke-width:1;vector-effect:non-scaling-stroke}.strct-chart__reset{position:absolute;top:6px;right:8px;z-index:3;display:inline-flex;align-items:center;gap:5px;padding:2px 9px;border:1px solid var(--b2);border-radius:99px;background:var(--bg-a);color:var(--t2);font-family:var(--font);font-size:12px;font-weight:600;cursor:pointer}.strct-chart__reset:hover{color:var(--t1);border-color:var(--acc50)}.strct-chart__reset:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-chart__tip--gap{top:6px;transform:translate(-50%)}.strct-chart__tip-ann{font-size:12px;font-weight:600}.strct-chart--glow .strct-chart__line{filter:drop-shadow(0 0 1.5px var(--strct-chart-c)) drop-shadow(0 0 5px var(--strct-chart-c))}.strct-chart--glow .strct-chart__head-dot{filter:drop-shadow(0 0 2px var(--strct-chart-c)) drop-shadow(0 0 6px var(--strct-chart-c))}.strct-chart--glow .strct-chart__hoverdot{filter:drop-shadow(0 0 4px var(--strct-chart-c))}.strct-chart__ytick{position:absolute;left:0;width:34px;text-align:end;transform:translateY(-50%);pointer-events:none;font-family:var(--mono);font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__thr{position:absolute;right:2px;transform:translateY(-50%);pointer-events:none;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;background:var(--bg-1);padding:0 3px;border-radius:3px}.strct-chart__axis-y{position:absolute;left:0;transform:translateY(-50%);pointer-events:none;padding:1px 5px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);font-family:var(--mono);font-size:12px;font-weight:600;color:var(--t2);font-variant-numeric:tabular-nums;z-index:2}.strct-chart__label--active{color:var(--t1);font-weight:700}.strct-chart__legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin-bottom:8px;font-size:12px;color:var(--t2)}.strct-chart__leg{display:inline-flex;align-items:center;gap:6px}.strct-chart__leg-sw{width:9px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip{position:absolute;transform:translate(-50%,calc(-100% - 10px));pointer-events:none;display:flex;flex-direction:column;align-items:center;gap:1px;padding:4px 8px;border-radius:var(--radius-sm);background:var(--bg-a);border:1px solid var(--b2);box-shadow:var(--shadow-elevated);white-space:nowrap;z-index:2}.strct-chart__tip--multi{top:6px;transform:translate(-50%);align-items:stretch;gap:3px}.strct-chart__tip-v{font-size:12px;font-weight:700;color:var(--t1);font-variant-numeric:tabular-nums}.strct-chart__tip-meta{display:inline-flex;align-items:center;gap:5px}.strct-chart__tip-delta{font-size:12px;font-weight:600;color:var(--t3);font-variant-numeric:tabular-nums}.strct-chart__tip-delta--up{color:var(--success)}.strct-chart__tip-delta--down{color:var(--critical)}.strct-chart__tip-l{font-size:12px;color:var(--t3)}.strct-chart__tip-row{display:inline-flex;align-items:center;gap:6px;font-size:12px}.strct-chart__tip-sw{width:8px;height:3px;border-radius:2px;flex-shrink:0}.strct-chart__tip-rl{color:var(--t3);margin-inline-end:auto}.strct-chart__tip-rv{color:var(--t1);font-weight:700;font-variant-numeric:tabular-nums}.strct-chart__labels{position:relative;height:15px;margin-top:6px;font-size:12px;color:var(--t3)}.strct-chart__labels span{position:absolute;transform:translate(-50%);white-space:nowrap}@media(prefers-reduced-motion:no-preference){.strct-chart__line--draw{stroke-dasharray:1;stroke-dashoffset:1;animation:strct-chart-draw .9s ease forwards}.strct-chart__pulse{transform-box:fill-box;transform-origin:center;animation:strct-chart-pulse 2.4s ease-out infinite}}@keyframes strct-chart-draw{to{stroke-dashoffset:0}}@keyframes strct-chart-pulse{0%{transform:scale(1);opacity:.45}70%{opacity:0}to{transform:scale(2.5);opacity:0}}\n"] }]
9989
+ }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], series: [{ type: i0.Input, args: [{ isSignal: true, alias: "series", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], curve: [{ type: i0.Input, args: [{ isSignal: true, alias: "curve", required: false }] }], area: [{ type: i0.Input, args: [{ isSignal: true, alias: "area", required: false }] }], glow: [{ type: i0.Input, args: [{ isSignal: true, alias: "glow", required: false }] }], live: [{ type: i0.Input, args: [{ isSignal: true, alias: "live", required: false }] }], interval: [{ type: i0.Input, args: [{ isSignal: true, alias: "interval", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], dots: [{ type: i0.Input, args: [{ isSignal: true, alias: "dots", required: false }] }], legend: [{ type: i0.Input, args: [{ isSignal: true, alias: "legend", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], xTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "xTicks", required: false }] }], xFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "xFormat", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], yAxis: [{ type: i0.Input, args: [{ isSignal: true, alias: "yAxis", required: false }] }], yTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "yTicks", required: false }] }], axisFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisFormat", required: false }] }], thresholds: [{ type: i0.Input, args: [{ isSignal: true, alias: "thresholds", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], agoFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "agoFormat", required: false }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], annotations: [{ type: i0.Input, args: [{ isSignal: true, alias: "annotations", required: false }] }], activeIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIndex", required: false }] }], brush: [{ type: i0.Input, args: [{ isSignal: true, alias: "brush", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], gapText: [{ type: i0.Input, args: [{ isSignal: true, alias: "gapText", required: false }] }], resetLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "resetLabel", required: false }] }], hoverIndex: [{ type: i0.Output, args: ["hoverIndex"] }], brushChange: [{ type: i0.Output, args: ["brushChange"] }], svgRef: [{ type: i0.ViewChild, args: ['svg', { isSignal: true }] }] } });
9335
9990
 
9336
9991
  const PALETTE = [
9337
9992
  'var(--acc)',