ng-hub-ui-metrics 22.2.4 → 22.4.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![Angular](https://img.shields.io/badge/Angular-21%2B-red.svg)](https://angular.dev)
7
7
  [![License](https://img.shields.io/npm/l/ng-hub-ui-metrics.svg)](LICENSE)
8
8
 
9
- Read-only value visualizations for Angular 21+ — a linear **progress** bar, a graded **meter** gauge and a circular **ring** (gauge) — bundled into a single standalone, signal-driven library. Zero external dependencies; every colour and dimension is a `--hub-*` CSS custom property derived from the shared design-system tokens.
9
+ Read-only value visualizations for Angular 21+ — a linear **progress** bar, a graded **meter** gauge and a circular **ring** (gauge) — bundled into a single standalone, signal-driven library. Its only runtime dependency is `ng-hub-ui-utils` (a required peer, where the shared accent resolver lives); every colour and dimension is a `--hub-*` CSS custom property derived from the shared design-system tokens.
10
10
 
11
11
  ## Documentation and Live Examples
12
12
 
@@ -49,9 +49,14 @@ This library is part of the **ng-hub-ui** ecosystem:
49
49
  ### 1. Install
50
50
 
51
51
  ```bash
52
- npm install ng-hub-ui-metrics
52
+ npm install ng-hub-ui-metrics ng-hub-ui-utils
53
53
  ```
54
54
 
55
+ > **`ng-hub-ui-utils` is a required peer dependency** (`>=22.7.0`): `<hub-progress>`
56
+ > resolves its `color` input through the shared `resolveHubAccent` helper that lives
57
+ > there. Installing `ng-hub-ui-metrics` alone leaves an unresolved import. `ng add ng-hub-ui`
58
+ > pulls it in for you.
59
+
55
60
  > **Theming (recommended):** install the shared design tokens so the metrics —
56
61
  > and every other ng-hub-ui library — read the same palette and dark-mode colours:
57
62
  >
@@ -124,6 +129,7 @@ the value falls in. Host role `meter`.
124
129
  | `low` | `number \| undefined` | `undefined` | Upper limit of the "low" region (defaults to `min`). |
125
130
  | `high` | `number \| undefined` | `undefined` | Lower limit of the "high" region (defaults to `max`). |
126
131
  | `optimum` | `number \| undefined` | `undefined` | Preferred point (defaults to the midpoint). |
132
+ | `label` | `string` | `''` | Accessible label (`aria-label`). `role="meter"` is named by the author only, so set it. |
127
133
 
128
134
  The computed band is exposed as `data-band` (`low` / `optimum` / `high`) and
129
135
  maps to `--hub-meter-low-bg` / `--hub-meter-optimum-bg` / `--hub-meter-high-bg`.
@@ -137,19 +143,19 @@ projected via `<ng-content>`. Host role `meter`.
137
143
  |---|---|---|---|
138
144
  | `value` | `number` | `0` | Score as a `0..1` ratio or a `0..max` figure. |
139
145
  | `max` | `number` | `1` | Value is normalized against this. |
140
- | `size` | `number \| string` | `'4rem'` | Outer diameter (number → px). |
141
- | `thickness` | `number \| string` | `'0.5rem'` | Stroke width (number → px). |
146
+ | `size` | `number \| string \| undefined` | `undefined` | Outer diameter (number → px), applied inline when set. Omit it to let the `--hub-ring-size` token drive the diameter — it defaults to `4rem`. |
147
+ | `thickness` | `number \| string \| undefined` | `undefined` | Stroke width (number → px), applied inline when set. Omit it to let the `--hub-ring-thickness` token drive the stroke — it defaults to `0.5rem`. |
142
148
  | `thresholds` | `{ low?: number; high?: number }` | `undefined` | Recolours the arc by band. |
143
149
  | `showValue` | `boolean` | `true` | Render the rounded percentage in the centre. |
150
+ | `label` | `string` | `''` | Accessible label (`aria-label`). Neither the caption nor the percentage can name a `role="meter"`, so set it. |
144
151
 
145
152
  ---
146
153
 
147
154
  ## 🎨 Styling
148
155
 
149
156
  Every visual detail is a `--hub-*` CSS custom property. Each component declares
150
- its defaults on its own element (`:where(.hub-progress)` / `:where(.hub-meter)` /
151
- `:where(.hub-ring)`), so a value set **on the element** wins over one inherited
152
- from a bare ancestor. Set the tokens on the component element, or on a wrapper that
157
+ its defaults on its own host element, at zero specificity (`:where(:host)`), so a
158
+ value set **on the element** wins over one inherited from a bare ancestor. Set the tokens on the component element, or on a wrapper that
153
159
  *targets the components as descendants* (`.wrapper :where(.hub-ring) { … }`) — which
154
160
  is exactly what the `hub-metrics-theme()` mixin emits. For `<hub-progress>` prefer the
155
161
  per-instance `color` input for one-off semantic tints.
@@ -158,6 +164,7 @@ per-instance `color` input for one-off semantic tints.
158
164
 
159
165
  | Token | Default | Description |
160
166
  |---|---|---|
167
+ | `--hub-progress-accent` | `--hub-sys-color-primary` | Root accent the other slots derive from; what the `color` input and the mixin's `$accent` write. |
161
168
  | `--hub-progress-track-bg` | tint of the accent | Track background. |
162
169
  | `--hub-progress-indicator-bg` | `--hub-progress-accent` | Filled indicator colour. |
163
170
  | `--hub-progress-height` | `0.5rem` | Bar thickness. |
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, booleanAttribute, computed, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { input, booleanAttribute, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { resolveHubAccent } from 'ng-hub-ui-utils';
4
4
 
5
5
  /**
@@ -82,11 +82,11 @@ class HubProgressComponent {
82
82
  .join(' '), /* @ts-ignore */
83
83
  ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
84
84
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubProgressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
85
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: HubProgressComponent, isStandalone: true, selector: "hub-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "progressbar" }, properties: { "class": "hostClasses()", "style.--hub-progress-accent": "accentVar()", "style.--hub-progress-value": "valueCss()", "attr.aria-valuemin": "0", "attr.aria-valuemax": "max()", "attr.aria-valuenow": "ariaValueNow()", "attr.aria-label": "label() || null", "attr.aria-busy": "indeterminate() || null" }, classAttribute: "hub-progress" }, ngImport: i0, template: "@if (label()) {\n\t<span class=\"hub-progress__label\">{{ label() }}</span>\n}\n\n<span class=\"hub-progress__track\">\n\t<span class=\"hub-progress__indicator\"></span>\n</span>\n\n@if (showValue() && !indeterminate()) {\n\t<span class=\"hub-progress__value\">{{ displayValue() }}</span>\n}\n", styles: [":where(.hub-progress){--hub-progress-accent: var(--hub-sys-color-primary, #0d6efd);--hub-progress-track-bg: color-mix(in oklch, var(--hub-progress-accent) 16%, transparent);--hub-progress-indicator-bg: var(--hub-progress-accent);--hub-progress-height: var(--hub-ref-space-2, .5rem);--hub-progress-radius: var(--hub-ref-radius-pill, 50rem)}.hub-progress{display:flex;align-items:center;gap:var(--hub-ref-space-2, .5rem);inline-size:100%}.hub-progress__label{flex:0 0 auto;color:var(--hub-sys-text-muted, #6c757d);font-size:var(--hub-ref-font-size-sm, .875rem)}.hub-progress__track{position:relative;flex:1 1 auto;block-size:var(--hub-progress-height);overflow:hidden;background:var(--hub-progress-track-bg);border-radius:var(--hub-progress-radius)}.hub-progress__indicator{display:block;inline-size:var(--hub-progress-value, 0%);block-size:100%;background:var(--hub-progress-indicator-bg);border-radius:inherit;transition:inline-size .3s ease}.hub-progress__value{flex:0 0 auto;min-inline-size:3ch;color:var(--hub-sys-text-primary, #212529);font-size:var(--hub-ref-font-size-sm, .875rem);font-weight:var(--hub-ref-font-weight-semibold, 600);text-align:end;font-variant-numeric:tabular-nums}.hub-progress--sm{--hub-progress-height: var(--hub-ref-space-1, .25rem)}.hub-progress--md{--hub-progress-height: var(--hub-ref-space-2, .5rem)}.hub-progress--lg{--hub-progress-height: var(--hub-ref-space-3, .75rem)}.hub-progress--indeterminate .hub-progress__indicator{inline-size:40%;transition:none;animation:hub-progress-indeterminate 1.4s ease-in-out infinite}@keyframes hub-progress-indeterminate{0%{transform:translate(-100%)}to{transform:translate(300%)}}@media(prefers-reduced-motion:reduce){.hub-progress--indeterminate .hub-progress__indicator{animation-duration:3s}.hub-progress__indicator{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
85
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: HubProgressComponent, isStandalone: true, selector: "hub-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "progressbar" }, properties: { "class": "hostClasses()", "style.--hub-progress-accent": "accentVar()", "style.--hub-progress-value": "valueCss()", "attr.aria-valuemin": "0", "attr.aria-valuemax": "max()", "attr.aria-valuenow": "ariaValueNow()", "attr.aria-label": "label() || null", "attr.aria-busy": "indeterminate() || null" }, classAttribute: "hub-progress" }, ngImport: i0, template: "@if (label()) {\n\t<span class=\"hub-progress__label\">{{ label() }}</span>\n}\n\n<span class=\"hub-progress__track\">\n\t<span class=\"hub-progress__indicator\"></span>\n</span>\n\n@if (showValue() && !indeterminate()) {\n\t<span class=\"hub-progress__value\">{{ displayValue() }}</span>\n}\n", styles: [":where(:host){--hub-progress-accent: var(--hub-sys-color-primary, #0d6efd);--hub-progress-track-bg: color-mix(in oklch, var(--hub-progress-accent) 16%, transparent);--hub-progress-indicator-bg: var(--hub-progress-accent);--hub-progress-height: var(--hub-ref-space-2, .5rem);--hub-progress-radius: var(--hub-ref-radius-pill, 50rem)}:host{display:flex;align-items:center;gap:var(--hub-ref-space-2, .5rem);inline-size:100%}.hub-progress__label{flex:0 0 auto;color:var(--hub-sys-text-muted, #6c757d);font-size:var(--hub-ref-font-size-sm, .875rem)}.hub-progress__track{position:relative;flex:1 1 auto;block-size:var(--hub-progress-height);overflow:hidden;background:var(--hub-progress-track-bg);border-radius:var(--hub-progress-radius)}.hub-progress__indicator{display:block;inline-size:var(--hub-progress-value, 0%);block-size:100%;background:var(--hub-progress-indicator-bg);border-radius:inherit;transition:inline-size .3s ease}.hub-progress__value{flex:0 0 auto;min-inline-size:3ch;color:var(--hub-sys-text-primary, #212529);font-size:var(--hub-ref-font-size-sm, .875rem);font-weight:var(--hub-ref-font-weight-semibold, 600);text-align:end;font-variant-numeric:tabular-nums}:host(.hub-progress--sm){--hub-progress-height: var(--hub-ref-space-1, .25rem)}:host(.hub-progress--md){--hub-progress-height: var(--hub-ref-space-2, .5rem)}:host(.hub-progress--lg){--hub-progress-height: var(--hub-ref-space-3, .75rem)}:host(.hub-progress--indeterminate) .hub-progress__indicator{inline-size:40%;transition:none;animation:hub-progress-indeterminate 1.4s ease-in-out infinite}@keyframes hub-progress-indeterminate{0%{transform:translate(-100%)}to{transform:translate(300%)}}@media(prefers-reduced-motion:reduce){:host(.hub-progress--indeterminate) .hub-progress__indicator{animation-duration:3s}.hub-progress__indicator{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
86
86
  }
87
87
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubProgressComponent, decorators: [{
88
88
  type: Component,
89
- args: [{ selector: 'hub-progress', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
89
+ args: [{ selector: 'hub-progress', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
90
90
  class: 'hub-progress',
91
91
  role: 'progressbar',
92
92
  '[class]': 'hostClasses()',
@@ -97,7 +97,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
97
97
  '[attr.aria-valuenow]': 'ariaValueNow()',
98
98
  '[attr.aria-label]': 'label() || null',
99
99
  '[attr.aria-busy]': 'indeterminate() || null'
100
- }, template: "@if (label()) {\n\t<span class=\"hub-progress__label\">{{ label() }}</span>\n}\n\n<span class=\"hub-progress__track\">\n\t<span class=\"hub-progress__indicator\"></span>\n</span>\n\n@if (showValue() && !indeterminate()) {\n\t<span class=\"hub-progress__value\">{{ displayValue() }}</span>\n}\n", styles: [":where(.hub-progress){--hub-progress-accent: var(--hub-sys-color-primary, #0d6efd);--hub-progress-track-bg: color-mix(in oklch, var(--hub-progress-accent) 16%, transparent);--hub-progress-indicator-bg: var(--hub-progress-accent);--hub-progress-height: var(--hub-ref-space-2, .5rem);--hub-progress-radius: var(--hub-ref-radius-pill, 50rem)}.hub-progress{display:flex;align-items:center;gap:var(--hub-ref-space-2, .5rem);inline-size:100%}.hub-progress__label{flex:0 0 auto;color:var(--hub-sys-text-muted, #6c757d);font-size:var(--hub-ref-font-size-sm, .875rem)}.hub-progress__track{position:relative;flex:1 1 auto;block-size:var(--hub-progress-height);overflow:hidden;background:var(--hub-progress-track-bg);border-radius:var(--hub-progress-radius)}.hub-progress__indicator{display:block;inline-size:var(--hub-progress-value, 0%);block-size:100%;background:var(--hub-progress-indicator-bg);border-radius:inherit;transition:inline-size .3s ease}.hub-progress__value{flex:0 0 auto;min-inline-size:3ch;color:var(--hub-sys-text-primary, #212529);font-size:var(--hub-ref-font-size-sm, .875rem);font-weight:var(--hub-ref-font-weight-semibold, 600);text-align:end;font-variant-numeric:tabular-nums}.hub-progress--sm{--hub-progress-height: var(--hub-ref-space-1, .25rem)}.hub-progress--md{--hub-progress-height: var(--hub-ref-space-2, .5rem)}.hub-progress--lg{--hub-progress-height: var(--hub-ref-space-3, .75rem)}.hub-progress--indeterminate .hub-progress__indicator{inline-size:40%;transition:none;animation:hub-progress-indeterminate 1.4s ease-in-out infinite}@keyframes hub-progress-indeterminate{0%{transform:translate(-100%)}to{transform:translate(300%)}}@media(prefers-reduced-motion:reduce){.hub-progress--indeterminate .hub-progress__indicator{animation-duration:3s}.hub-progress__indicator{transition:none}}\n"] }]
100
+ }, template: "@if (label()) {\n\t<span class=\"hub-progress__label\">{{ label() }}</span>\n}\n\n<span class=\"hub-progress__track\">\n\t<span class=\"hub-progress__indicator\"></span>\n</span>\n\n@if (showValue() && !indeterminate()) {\n\t<span class=\"hub-progress__value\">{{ displayValue() }}</span>\n}\n", styles: [":where(:host){--hub-progress-accent: var(--hub-sys-color-primary, #0d6efd);--hub-progress-track-bg: color-mix(in oklch, var(--hub-progress-accent) 16%, transparent);--hub-progress-indicator-bg: var(--hub-progress-accent);--hub-progress-height: var(--hub-ref-space-2, .5rem);--hub-progress-radius: var(--hub-ref-radius-pill, 50rem)}:host{display:flex;align-items:center;gap:var(--hub-ref-space-2, .5rem);inline-size:100%}.hub-progress__label{flex:0 0 auto;color:var(--hub-sys-text-muted, #6c757d);font-size:var(--hub-ref-font-size-sm, .875rem)}.hub-progress__track{position:relative;flex:1 1 auto;block-size:var(--hub-progress-height);overflow:hidden;background:var(--hub-progress-track-bg);border-radius:var(--hub-progress-radius)}.hub-progress__indicator{display:block;inline-size:var(--hub-progress-value, 0%);block-size:100%;background:var(--hub-progress-indicator-bg);border-radius:inherit;transition:inline-size .3s ease}.hub-progress__value{flex:0 0 auto;min-inline-size:3ch;color:var(--hub-sys-text-primary, #212529);font-size:var(--hub-ref-font-size-sm, .875rem);font-weight:var(--hub-ref-font-weight-semibold, 600);text-align:end;font-variant-numeric:tabular-nums}:host(.hub-progress--sm){--hub-progress-height: var(--hub-ref-space-1, .25rem)}:host(.hub-progress--md){--hub-progress-height: var(--hub-ref-space-2, .5rem)}:host(.hub-progress--lg){--hub-progress-height: var(--hub-ref-space-3, .75rem)}:host(.hub-progress--indeterminate) .hub-progress__indicator{inline-size:40%;transition:none;animation:hub-progress-indeterminate 1.4s ease-in-out infinite}@keyframes hub-progress-indeterminate{0%{transform:translate(-100%)}to{transform:translate(300%)}}@media(prefers-reduced-motion:reduce){:host(.hub-progress--indeterminate) .hub-progress__indicator{animation-duration:3s}.hub-progress__indicator{transition:none}}\n"] }]
101
101
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
102
102
 
103
103
  /**
@@ -129,6 +129,13 @@ class HubMeterComponent {
129
129
  /** Preferred point of the range; defaults to the midpoint when omitted. */
130
130
  optimum = input(undefined, /* @ts-ignore */
131
131
  ...(ngDevMode ? [{ debugName: "optimum" }] : /* istanbul ignore next */ []));
132
+ /**
133
+ * Accessible label describing what is being measured. `role="meter"` takes its
134
+ * name from the author only, so without it the gauge reaches assistive
135
+ * technology unnamed — the component renders no text of its own.
136
+ */
137
+ label = input('', /* @ts-ignore */
138
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
132
139
  /** Fully resolved, clamped bounds derived from the raw inputs. */
133
140
  resolved = computed(() => {
134
141
  const min = this.min();
@@ -165,11 +172,11 @@ class HubMeterComponent {
165
172
  hostClasses = computed(() => ['hub-meter', `hub-meter--${this.band()}`].join(' '), /* @ts-ignore */
166
173
  ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
167
174
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubMeterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
168
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: HubMeterComponent, isStandalone: true, selector: "hub-meter", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, low: { classPropertyName: "low", publicName: "low", isSignal: true, isRequired: false, transformFunction: null }, high: { classPropertyName: "high", publicName: "high", isSignal: true, isRequired: false, transformFunction: null }, optimum: { classPropertyName: "optimum", publicName: "optimum", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "meter" }, properties: { "class": "hostClasses()", "attr.data-band": "band()", "style.--hub-meter-value": "valueCss()", "attr.aria-valuemin": "resolved().min", "attr.aria-valuemax": "resolved().max", "attr.aria-valuenow": "resolved().value", "attr.aria-valuetext": "valueText()" }, classAttribute: "hub-meter" }, ngImport: i0, template: "<span class=\"hub-meter__track\">\n\t<span class=\"hub-meter__indicator\"></span>\n</span>\n", styles: [":where(.hub-meter){--hub-meter-track-bg: color-mix(in oklch, var(--hub-sys-color-neutral, #6c757d) 16%, transparent);--hub-meter-low-bg: var(--hub-sys-color-danger, #dc3545);--hub-meter-optimum-bg: var(--hub-sys-color-success, #198754);--hub-meter-high-bg: var(--hub-sys-color-warning, #ffc107);--hub-meter-height: var(--hub-ref-space-2, .5rem);--hub-meter-radius: var(--hub-ref-radius-pill, 50rem);--hub-meter-fill: var(--hub-meter-optimum-bg)}.hub-meter{display:block;inline-size:100%}.hub-meter__track{position:relative;display:block;block-size:var(--hub-meter-height);overflow:hidden;background:var(--hub-meter-track-bg);border-radius:var(--hub-meter-radius)}.hub-meter__indicator{display:block;inline-size:var(--hub-meter-value, 0%);block-size:100%;background:var(--hub-meter-fill);border-radius:inherit;transition:inline-size .3s ease,background-color .3s ease}.hub-meter--low{--hub-meter-fill: var(--hub-meter-low-bg)}.hub-meter--optimum{--hub-meter-fill: var(--hub-meter-optimum-bg)}.hub-meter--high{--hub-meter-fill: var(--hub-meter-high-bg)}@media(prefers-reduced-motion:reduce){.hub-meter__indicator{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
175
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: HubMeterComponent, isStandalone: true, selector: "hub-meter", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, low: { classPropertyName: "low", publicName: "low", isSignal: true, isRequired: false, transformFunction: null }, high: { classPropertyName: "high", publicName: "high", isSignal: true, isRequired: false, transformFunction: null }, optimum: { classPropertyName: "optimum", publicName: "optimum", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "meter" }, properties: { "class": "hostClasses()", "attr.data-band": "band()", "style.--hub-meter-value": "valueCss()", "attr.aria-valuemin": "resolved().min", "attr.aria-valuemax": "resolved().max", "attr.aria-valuenow": "resolved().value", "attr.aria-valuetext": "valueText()", "attr.aria-label": "label() || null" }, classAttribute: "hub-meter" }, ngImport: i0, template: "<span class=\"hub-meter__track\">\n\t<span class=\"hub-meter__indicator\"></span>\n</span>\n", styles: [":where(:host){--hub-meter-track-bg: color-mix(in oklch, var(--hub-sys-color-neutral, #6c757d) 16%, transparent);--hub-meter-low-bg: var(--hub-sys-color-danger, #dc3545);--hub-meter-optimum-bg: var(--hub-sys-color-success, #198754);--hub-meter-high-bg: var(--hub-sys-color-warning, #ffc107);--hub-meter-height: var(--hub-ref-space-2, .5rem);--hub-meter-radius: var(--hub-ref-radius-pill, 50rem);--hub-meter-fill: var(--hub-meter-optimum-bg)}:host{display:block;inline-size:100%}.hub-meter__track{position:relative;display:block;block-size:var(--hub-meter-height);overflow:hidden;background:var(--hub-meter-track-bg);border-radius:var(--hub-meter-radius)}.hub-meter__indicator{display:block;inline-size:var(--hub-meter-value, 0%);block-size:100%;background:var(--hub-meter-fill);border-radius:inherit;transition:inline-size .3s ease,background-color .3s ease}:host(.hub-meter--low){--hub-meter-fill: var(--hub-meter-low-bg)}:host(.hub-meter--optimum){--hub-meter-fill: var(--hub-meter-optimum-bg)}:host(.hub-meter--high){--hub-meter-fill: var(--hub-meter-high-bg)}@media(prefers-reduced-motion:reduce){.hub-meter__indicator{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
169
176
  }
170
177
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubMeterComponent, decorators: [{
171
178
  type: Component,
172
- args: [{ selector: 'hub-meter', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
179
+ args: [{ selector: 'hub-meter', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
173
180
  class: 'hub-meter',
174
181
  role: 'meter',
175
182
  '[class]': 'hostClasses()',
@@ -178,9 +185,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
178
185
  '[attr.aria-valuemin]': 'resolved().min',
179
186
  '[attr.aria-valuemax]': 'resolved().max',
180
187
  '[attr.aria-valuenow]': 'resolved().value',
181
- '[attr.aria-valuetext]': 'valueText()'
182
- }, template: "<span class=\"hub-meter__track\">\n\t<span class=\"hub-meter__indicator\"></span>\n</span>\n", styles: [":where(.hub-meter){--hub-meter-track-bg: color-mix(in oklch, var(--hub-sys-color-neutral, #6c757d) 16%, transparent);--hub-meter-low-bg: var(--hub-sys-color-danger, #dc3545);--hub-meter-optimum-bg: var(--hub-sys-color-success, #198754);--hub-meter-high-bg: var(--hub-sys-color-warning, #ffc107);--hub-meter-height: var(--hub-ref-space-2, .5rem);--hub-meter-radius: var(--hub-ref-radius-pill, 50rem);--hub-meter-fill: var(--hub-meter-optimum-bg)}.hub-meter{display:block;inline-size:100%}.hub-meter__track{position:relative;display:block;block-size:var(--hub-meter-height);overflow:hidden;background:var(--hub-meter-track-bg);border-radius:var(--hub-meter-radius)}.hub-meter__indicator{display:block;inline-size:var(--hub-meter-value, 0%);block-size:100%;background:var(--hub-meter-fill);border-radius:inherit;transition:inline-size .3s ease,background-color .3s ease}.hub-meter--low{--hub-meter-fill: var(--hub-meter-low-bg)}.hub-meter--optimum{--hub-meter-fill: var(--hub-meter-optimum-bg)}.hub-meter--high{--hub-meter-fill: var(--hub-meter-high-bg)}@media(prefers-reduced-motion:reduce){.hub-meter__indicator{transition:none}}\n"] }]
183
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], low: [{ type: i0.Input, args: [{ isSignal: true, alias: "low", required: false }] }], high: [{ type: i0.Input, args: [{ isSignal: true, alias: "high", required: false }] }], optimum: [{ type: i0.Input, args: [{ isSignal: true, alias: "optimum", required: false }] }] } });
188
+ '[attr.aria-valuetext]': 'valueText()',
189
+ '[attr.aria-label]': 'label() || null'
190
+ }, template: "<span class=\"hub-meter__track\">\n\t<span class=\"hub-meter__indicator\"></span>\n</span>\n", styles: [":where(:host){--hub-meter-track-bg: color-mix(in oklch, var(--hub-sys-color-neutral, #6c757d) 16%, transparent);--hub-meter-low-bg: var(--hub-sys-color-danger, #dc3545);--hub-meter-optimum-bg: var(--hub-sys-color-success, #198754);--hub-meter-high-bg: var(--hub-sys-color-warning, #ffc107);--hub-meter-height: var(--hub-ref-space-2, .5rem);--hub-meter-radius: var(--hub-ref-radius-pill, 50rem);--hub-meter-fill: var(--hub-meter-optimum-bg)}:host{display:block;inline-size:100%}.hub-meter__track{position:relative;display:block;block-size:var(--hub-meter-height);overflow:hidden;background:var(--hub-meter-track-bg);border-radius:var(--hub-meter-radius)}.hub-meter__indicator{display:block;inline-size:var(--hub-meter-value, 0%);block-size:100%;background:var(--hub-meter-fill);border-radius:inherit;transition:inline-size .3s ease,background-color .3s ease}:host(.hub-meter--low){--hub-meter-fill: var(--hub-meter-low-bg)}:host(.hub-meter--optimum){--hub-meter-fill: var(--hub-meter-optimum-bg)}:host(.hub-meter--high){--hub-meter-fill: var(--hub-meter-high-bg)}@media(prefers-reduced-motion:reduce){.hub-meter__indicator{transition:none}}\n"] }]
191
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], low: [{ type: i0.Input, args: [{ isSignal: true, alias: "low", required: false }] }], high: [{ type: i0.Input, args: [{ isSignal: true, alias: "high", required: false }] }], optimum: [{ type: i0.Input, args: [{ isSignal: true, alias: "optimum", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
184
192
 
185
193
  /**
186
194
  * Circular gauge (ring) rendering a normalized score as an SVG arc.
@@ -190,11 +198,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
190
198
  * over a `pathLength`-normalized circle, so the offset maps linearly to the
191
199
  * completion (100 at 0%, 0 at 100%). Optional {@link thresholds} recolour the
192
200
  * indicator through the `--hub-ring-low-color` / `--hub-ring-high-color` tokens;
193
- * a centred caption is projected through `<ng-content>`.
201
+ * a centred caption is projected through `<ng-content>`. That caption is decoration:
202
+ * `role="meter"` is named by the author, so pass {@link label} to name the gauge.
194
203
  *
195
204
  * @example
196
205
  * ```html
197
- * <hub-ring [value]="0.82" [thresholds]="{ low: 0.4, high: 0.75 }">Score</hub-ring>
206
+ * <hub-ring [value]="0.82" [thresholds]="{ low: 0.4, high: 0.75 }" label="Quality score">Score</hub-ring>
198
207
  * ```
199
208
  */
200
209
  class HubRingComponent {
@@ -204,17 +213,32 @@ class HubRingComponent {
204
213
  /** Upper bound the {@link value} is normalized against. */
205
214
  max = input(1, /* @ts-ignore */
206
215
  ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
207
- /** Outer diameter of the ring (number → px, or any CSS length string). */
208
- size = input('4rem', /* @ts-ignore */
216
+ /**
217
+ * Outer diameter of the ring (number → px, or any CSS length string), applied
218
+ * inline as a per-instance override. Omit it to let the `--hub-ring-size`
219
+ * token (default or theme override) drive the diameter.
220
+ */
221
+ size = input(undefined, /* @ts-ignore */
209
222
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
210
- /** Stroke width of the ring (number → px, or any CSS length string). */
211
- thickness = input('0.5rem', /* @ts-ignore */
223
+ /**
224
+ * Stroke width of the ring (number → px, or any CSS length string), applied
225
+ * inline as a per-instance override. Omit it to let the
226
+ * `--hub-ring-thickness` token (default or theme override) drive the stroke.
227
+ */
228
+ thickness = input(undefined, /* @ts-ignore */
212
229
  ...(ngDevMode ? [{ debugName: "thickness" }] : /* istanbul ignore next */ []));
213
230
  /** Optional colour thresholds recolouring the indicator by band. */
214
231
  thresholds = input(undefined, /* @ts-ignore */
215
232
  ...(ngDevMode ? [{ debugName: "thresholds" }] : /* istanbul ignore next */ []));
216
233
  /** Whether the rounded percentage is rendered in the centre. */
217
234
  showValue = input(true, { ...(ngDevMode ? { debugName: "showValue" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
235
+ /**
236
+ * Accessible label describing what the ring scores. `role="meter"` takes its
237
+ * name from the author only, so neither the projected caption nor the centred
238
+ * percentage can name the gauge — this input is the only way to do it.
239
+ */
240
+ label = input('', /* @ts-ignore */
241
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
218
242
  /** Total normalized path length of the SVG circle (see `pathLength`). */
219
243
  PATH_LENGTH = 100;
220
244
  /** Completion ratio in `[0, 1]`; `0` when `max` is not positive. */
@@ -263,16 +287,23 @@ class HubRingComponent {
263
287
  /** Stroke thickness as a CSS length (numbers are treated as pixels). */
264
288
  thicknessCss = computed(() => this.toCssLength(this.thickness()), /* @ts-ignore */
265
289
  ...(ngDevMode ? [{ debugName: "thicknessCss" }] : /* istanbul ignore next */ []));
266
- /** Normalizes a number|string dimension into a CSS length string. */
290
+ /**
291
+ * Normalizes a number|string dimension into a CSS length string, returning
292
+ * `null` when the input is unset so the inline binding is dropped altogether
293
+ * and the stylesheet token wins the cascade instead of being shadowed.
294
+ */
267
295
  toCssLength(dimension) {
296
+ if (dimension === undefined) {
297
+ return null;
298
+ }
268
299
  return typeof dimension === 'number' ? `${dimension}px` : dimension;
269
300
  }
270
301
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubRingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
271
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: HubRingComponent, isStandalone: true, selector: "hub-ring", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, thickness: { classPropertyName: "thickness", publicName: "thickness", isSignal: true, isRequired: false, transformFunction: null }, thresholds: { classPropertyName: "thresholds", publicName: "thresholds", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "meter" }, properties: { "class": "hostClasses()", "style.--hub-ring-size": "sizeCss()", "style.--hub-ring-thickness": "thicknessCss()", "attr.aria-valuemin": "0", "attr.aria-valuemax": "max()", "attr.aria-valuenow": "value()", "attr.aria-valuetext": "displayValue()", "attr.aria-label": "displayValue()" }, classAttribute: "hub-ring" }, exportAs: ["hubGauge"], ngImport: i0, template: "<svg class=\"hub-ring__svg\" viewBox=\"0 0 100 100\" aria-hidden=\"true\" focusable=\"false\">\n\t<circle class=\"hub-ring__track\" cx=\"50\" cy=\"50\" r=\"44\" [attr.pathLength]=\"PATH_LENGTH\" />\n\t<circle\n\t\tclass=\"hub-ring__indicator\"\n\t\tcx=\"50\"\n\t\tcy=\"50\"\n\t\tr=\"44\"\n\t\t[attr.pathLength]=\"PATH_LENGTH\"\n\t\t[attr.stroke-dasharray]=\"dashArray()\"\n\t\t[attr.stroke-dashoffset]=\"dashOffset()\"\n\t/>\n</svg>\n\n<span class=\"hub-ring__caption\">\n\t@if (showValue()) {\n\t\t<span class=\"hub-ring__value\">{{ displayValue() }}</span>\n\t}\n\t<ng-content />\n</span>\n", styles: [":where(.hub-ring){--hub-ring-size: 4rem;--hub-ring-thickness: var(--hub-ref-space-2, .5rem);--hub-ring-track: color-mix(in oklch, var(--hub-sys-color-neutral, #6c757d) 20%, transparent);--hub-ring-indicator: var(--hub-sys-color-primary, #0d6efd);--hub-ring-caption-color: var(--hub-sys-text-primary, #212529);--hub-ring-low-color: var(--hub-sys-color-danger, #dc3545);--hub-ring-high-color: var(--hub-sys-color-success, #198754)}.hub-ring{position:relative;display:inline-flex;align-items:center;justify-content:center;inline-size:var(--hub-ring-size);block-size:var(--hub-ring-size)}.hub-ring__svg{inline-size:100%;block-size:100%;transform:rotate(-90deg);overflow:visible}.hub-ring__track,.hub-ring__indicator{fill:none;stroke-width:var(--hub-ring-thickness);stroke-linecap:round}.hub-ring__track{stroke:var(--hub-ring-track)}.hub-ring__indicator{stroke:var(--hub-ring-indicator);transition:stroke-dashoffset .4s ease,stroke .3s ease}.hub-ring__caption{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.125rem;color:var(--hub-ring-caption-color);text-align:center;line-height:1.1;pointer-events:none}.hub-ring__value{font-size:calc(var(--hub-ring-size) * .24);font-weight:var(--hub-ref-font-weight-semibold, 600);font-variant-numeric:tabular-nums}.hub-ring--low{--hub-ring-indicator: var(--hub-ring-low-color)}.hub-ring--high{--hub-ring-indicator: var(--hub-ring-high-color)}@media(prefers-reduced-motion:reduce){.hub-ring__indicator{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
302
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: HubRingComponent, isStandalone: true, selector: "hub-ring", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, thickness: { classPropertyName: "thickness", publicName: "thickness", isSignal: true, isRequired: false, transformFunction: null }, thresholds: { classPropertyName: "thresholds", publicName: "thresholds", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "meter" }, properties: { "class": "hostClasses()", "style.--hub-ring-size": "sizeCss()", "style.--hub-ring-thickness": "thicknessCss()", "attr.aria-valuemin": "0", "attr.aria-valuemax": "max()", "attr.aria-valuenow": "value()", "attr.aria-valuetext": "displayValue()", "attr.aria-label": "label() || null" }, classAttribute: "hub-ring" }, exportAs: ["hubGauge"], ngImport: i0, template: "<svg class=\"hub-ring__svg\" viewBox=\"0 0 100 100\" aria-hidden=\"true\" focusable=\"false\">\n\t<circle class=\"hub-ring__track\" cx=\"50\" cy=\"50\" r=\"44\" [attr.pathLength]=\"PATH_LENGTH\" />\n\t<circle\n\t\tclass=\"hub-ring__indicator\"\n\t\tcx=\"50\"\n\t\tcy=\"50\"\n\t\tr=\"44\"\n\t\t[attr.pathLength]=\"PATH_LENGTH\"\n\t\t[attr.stroke-dasharray]=\"dashArray()\"\n\t\t[attr.stroke-dashoffset]=\"dashOffset()\"\n\t/>\n</svg>\n\n<span class=\"hub-ring__caption\">\n\t@if (showValue()) {\n\t\t<span class=\"hub-ring__value\">{{ displayValue() }}</span>\n\t}\n\t<ng-content />\n</span>\n", styles: [":where(:host){--hub-ring-size: 4rem;--hub-ring-thickness: var(--hub-ref-space-2, .5rem);--hub-ring-track: color-mix(in oklch, var(--hub-sys-color-neutral, #6c757d) 20%, transparent);--hub-ring-indicator: var(--hub-sys-color-primary, #0d6efd);--hub-ring-caption-color: var(--hub-sys-text-primary, #212529);--hub-ring-low-color: var(--hub-sys-color-danger, #dc3545);--hub-ring-high-color: var(--hub-sys-color-success, #198754)}:host{position:relative;display:inline-flex;align-items:center;justify-content:center;inline-size:var(--hub-ring-size);block-size:var(--hub-ring-size)}.hub-ring__svg{inline-size:100%;block-size:100%;transform:rotate(-90deg);overflow:visible}.hub-ring__track,.hub-ring__indicator{fill:none;stroke-width:var(--hub-ring-thickness);stroke-linecap:round}.hub-ring__track{stroke:var(--hub-ring-track)}.hub-ring__indicator{stroke:var(--hub-ring-indicator);transition:stroke-dashoffset .4s ease,stroke .3s ease}.hub-ring__caption{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.125rem;color:var(--hub-ring-caption-color);text-align:center;line-height:1.1;pointer-events:none}.hub-ring__value{font-size:calc(var(--hub-ring-size) * .24);font-weight:var(--hub-ref-font-weight-semibold, 600);font-variant-numeric:tabular-nums}:host(.hub-ring--low){--hub-ring-indicator: var(--hub-ring-low-color)}:host(.hub-ring--high){--hub-ring-indicator: var(--hub-ring-high-color)}@media(prefers-reduced-motion:reduce){.hub-ring__indicator{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
272
303
  }
273
304
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubRingComponent, decorators: [{
274
305
  type: Component,
275
- args: [{ selector: 'hub-ring', exportAs: 'hubGauge', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
306
+ args: [{ selector: 'hub-ring', exportAs: 'hubGauge', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
276
307
  class: 'hub-ring',
277
308
  role: 'meter',
278
309
  '[class]': 'hostClasses()',
@@ -282,9 +313,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
282
313
  '[attr.aria-valuemax]': 'max()',
283
314
  '[attr.aria-valuenow]': 'value()',
284
315
  '[attr.aria-valuetext]': 'displayValue()',
285
- '[attr.aria-label]': 'displayValue()'
286
- }, template: "<svg class=\"hub-ring__svg\" viewBox=\"0 0 100 100\" aria-hidden=\"true\" focusable=\"false\">\n\t<circle class=\"hub-ring__track\" cx=\"50\" cy=\"50\" r=\"44\" [attr.pathLength]=\"PATH_LENGTH\" />\n\t<circle\n\t\tclass=\"hub-ring__indicator\"\n\t\tcx=\"50\"\n\t\tcy=\"50\"\n\t\tr=\"44\"\n\t\t[attr.pathLength]=\"PATH_LENGTH\"\n\t\t[attr.stroke-dasharray]=\"dashArray()\"\n\t\t[attr.stroke-dashoffset]=\"dashOffset()\"\n\t/>\n</svg>\n\n<span class=\"hub-ring__caption\">\n\t@if (showValue()) {\n\t\t<span class=\"hub-ring__value\">{{ displayValue() }}</span>\n\t}\n\t<ng-content />\n</span>\n", styles: [":where(.hub-ring){--hub-ring-size: 4rem;--hub-ring-thickness: var(--hub-ref-space-2, .5rem);--hub-ring-track: color-mix(in oklch, var(--hub-sys-color-neutral, #6c757d) 20%, transparent);--hub-ring-indicator: var(--hub-sys-color-primary, #0d6efd);--hub-ring-caption-color: var(--hub-sys-text-primary, #212529);--hub-ring-low-color: var(--hub-sys-color-danger, #dc3545);--hub-ring-high-color: var(--hub-sys-color-success, #198754)}.hub-ring{position:relative;display:inline-flex;align-items:center;justify-content:center;inline-size:var(--hub-ring-size);block-size:var(--hub-ring-size)}.hub-ring__svg{inline-size:100%;block-size:100%;transform:rotate(-90deg);overflow:visible}.hub-ring__track,.hub-ring__indicator{fill:none;stroke-width:var(--hub-ring-thickness);stroke-linecap:round}.hub-ring__track{stroke:var(--hub-ring-track)}.hub-ring__indicator{stroke:var(--hub-ring-indicator);transition:stroke-dashoffset .4s ease,stroke .3s ease}.hub-ring__caption{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.125rem;color:var(--hub-ring-caption-color);text-align:center;line-height:1.1;pointer-events:none}.hub-ring__value{font-size:calc(var(--hub-ring-size) * .24);font-weight:var(--hub-ref-font-weight-semibold, 600);font-variant-numeric:tabular-nums}.hub-ring--low{--hub-ring-indicator: var(--hub-ring-low-color)}.hub-ring--high{--hub-ring-indicator: var(--hub-ring-high-color)}@media(prefers-reduced-motion:reduce){.hub-ring__indicator{transition:none}}\n"] }]
287
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], thickness: [{ type: i0.Input, args: [{ isSignal: true, alias: "thickness", required: false }] }], thresholds: [{ type: i0.Input, args: [{ isSignal: true, alias: "thresholds", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }] } });
316
+ '[attr.aria-label]': 'label() || null'
317
+ }, template: "<svg class=\"hub-ring__svg\" viewBox=\"0 0 100 100\" aria-hidden=\"true\" focusable=\"false\">\n\t<circle class=\"hub-ring__track\" cx=\"50\" cy=\"50\" r=\"44\" [attr.pathLength]=\"PATH_LENGTH\" />\n\t<circle\n\t\tclass=\"hub-ring__indicator\"\n\t\tcx=\"50\"\n\t\tcy=\"50\"\n\t\tr=\"44\"\n\t\t[attr.pathLength]=\"PATH_LENGTH\"\n\t\t[attr.stroke-dasharray]=\"dashArray()\"\n\t\t[attr.stroke-dashoffset]=\"dashOffset()\"\n\t/>\n</svg>\n\n<span class=\"hub-ring__caption\">\n\t@if (showValue()) {\n\t\t<span class=\"hub-ring__value\">{{ displayValue() }}</span>\n\t}\n\t<ng-content />\n</span>\n", styles: [":where(:host){--hub-ring-size: 4rem;--hub-ring-thickness: var(--hub-ref-space-2, .5rem);--hub-ring-track: color-mix(in oklch, var(--hub-sys-color-neutral, #6c757d) 20%, transparent);--hub-ring-indicator: var(--hub-sys-color-primary, #0d6efd);--hub-ring-caption-color: var(--hub-sys-text-primary, #212529);--hub-ring-low-color: var(--hub-sys-color-danger, #dc3545);--hub-ring-high-color: var(--hub-sys-color-success, #198754)}:host{position:relative;display:inline-flex;align-items:center;justify-content:center;inline-size:var(--hub-ring-size);block-size:var(--hub-ring-size)}.hub-ring__svg{inline-size:100%;block-size:100%;transform:rotate(-90deg);overflow:visible}.hub-ring__track,.hub-ring__indicator{fill:none;stroke-width:var(--hub-ring-thickness);stroke-linecap:round}.hub-ring__track{stroke:var(--hub-ring-track)}.hub-ring__indicator{stroke:var(--hub-ring-indicator);transition:stroke-dashoffset .4s ease,stroke .3s ease}.hub-ring__caption{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.125rem;color:var(--hub-ring-caption-color);text-align:center;line-height:1.1;pointer-events:none}.hub-ring__value{font-size:calc(var(--hub-ring-size) * .24);font-weight:var(--hub-ref-font-weight-semibold, 600);font-variant-numeric:tabular-nums}:host(.hub-ring--low){--hub-ring-indicator: var(--hub-ring-low-color)}:host(.hub-ring--high){--hub-ring-indicator: var(--hub-ring-high-color)}@media(prefers-reduced-motion:reduce){.hub-ring__indicator{transition:none}}\n"] }]
318
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], thickness: [{ type: i0.Input, args: [{ isSignal: true, alias: "thickness", required: false }] }], thresholds: [{ type: i0.Input, args: [{ isSignal: true, alias: "thresholds", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
288
319
 
289
320
  /*
290
321
  * Public API Surface of ng-hub-ui-metrics
@@ -1 +1 @@
1
- {"version":3,"file":"ng-hub-ui-metrics.mjs","sources":["../../../projects/metrics/src/lib/components/progress/progress.component.ts","../../../projects/metrics/src/lib/components/progress/progress.component.html","../../../projects/metrics/src/lib/components/meter/meter.component.ts","../../../projects/metrics/src/lib/components/meter/meter.component.html","../../../projects/metrics/src/lib/components/ring/ring.component.ts","../../../projects/metrics/src/lib/components/ring/ring.component.html","../../../projects/metrics/src/public-api.ts","../../../projects/metrics/src/ng-hub-ui-metrics.ts"],"sourcesContent":["import { booleanAttribute, ChangeDetectionStrategy, Component, computed, input, ViewEncapsulation } from '@angular/core';\nimport { HubMetricsColor } from '../../models/metrics.types';\nimport { resolveHubAccent } from 'ng-hub-ui-utils';\nimport { HubProgressSize } from './progress.types';\n\n/**\n * Linear progress bar for a determinate or indeterminate read-only value.\n *\n * The component owns no hard-coded colour values: the semantic `color` input\n * maps to the host application's `--hub-sys-color-*` token family and drives the\n * indicator through the local `--hub-progress-accent` slot. When\n * {@link indeterminate} is set the bar loops a looping animation and drops\n * `aria-valuenow`, matching the ARIA `progressbar` contract for an unknown value.\n */\n@Component({\n\tselector: 'hub-progress',\n\tstandalone: true,\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\ttemplateUrl: './progress.component.html',\n\tstyleUrl: './progress.component.scss',\n\tencapsulation: ViewEncapsulation.None,\n\thost: {\n\t\tclass: 'hub-progress',\n\t\trole: 'progressbar',\n\t\t'[class]': 'hostClasses()',\n\t\t'[style.--hub-progress-accent]': 'accentVar()',\n\t\t'[style.--hub-progress-value]': 'valueCss()',\n\t\t'[attr.aria-valuemin]': '0',\n\t\t'[attr.aria-valuemax]': 'max()',\n\t\t'[attr.aria-valuenow]': 'ariaValueNow()',\n\t\t'[attr.aria-label]': 'label() || null',\n\t\t'[attr.aria-busy]': 'indeterminate() || null'\n\t}\n})\nexport class HubProgressComponent {\n\t/** Current progress value, expressed on the `[0, max]` scale. */\n\treadonly value = input<number>(0);\n\n\t/** Upper bound of the progress scale. */\n\treadonly max = input<number>(100);\n\n\t/** Renders a looping bar for work of unknown duration; drops `aria-valuenow`. */\n\treadonly indeterminate = input(false, { transform: booleanAttribute });\n\n\t/**\n\t * Optional accent driving the indicator colour. Accepts any colour: a bareword\n\t * (semantic name, registered accent or CSS named colour) maps to\n\t * `--hub-sys-color-<color>` with the word itself as the raw fallback, while a\n\t * literal (`#hex`, `rgb()`, `oklch()`, `var()`…) is passed through unchanged.\n\t * The resolved value is applied inline (a per-instance override); when omitted\n\t * the accent falls back to the `--hub-progress-accent` token, so a theme\n\t * (`hub-metrics-theme` mixin / ancestor override) can drive it instead.\n\t */\n\treadonly color = input<HubMetricsColor | undefined>(undefined);\n\n\t/** Size token controlling the bar thickness. */\n\treadonly size = input<HubProgressSize>('md');\n\n\t/** Whether the rounded percentage is rendered next to the bar. */\n\treadonly showValue = input(false, { transform: booleanAttribute });\n\n\t/** Accessible label describing what the progress bar measures. */\n\treadonly label = input<string>('');\n\n\t/** Value clamped to the `[0, max]` range; `0` when `max` is not positive. */\n\tprotected readonly clampedValue = computed(() => {\n\t\tconst max = this.max();\n\t\tif (max <= 0) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn Math.min(max, Math.max(0, this.value()));\n\t});\n\n\t/** Completion ratio as a `0..100` percentage; `0` while indeterminate. */\n\tprotected readonly percent = computed(() => {\n\t\tconst max = this.max();\n\t\tif (this.indeterminate() || max <= 0) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn (this.clampedValue() / max) * 100;\n\t});\n\n\t/** Indicator width as a CSS length consumed by `--hub-progress-value`. */\n\tprotected readonly valueCss = computed(() => `${this.percent()}%`);\n\n\t/** Rounded percentage label shown when {@link showValue} is enabled. */\n\tprotected readonly displayValue = computed(() => `${Math.round(this.percent())}%`);\n\n\t/** `aria-valuenow`, omitted (null) while the bar is {@link indeterminate}. */\n\tprotected readonly ariaValueNow = computed(() => (this.indeterminate() ? null : this.clampedValue()));\n\n\t/**\n\t * Local accent slot resolved from the {@link color} input. A bareword resolves\n\t * to `var(--hub-sys-color-<color>, <color>)` — the ds token with the word as a\n\t * raw fallback — while a literal colour (`#hex`, `rgb()`, `oklch()`, `var()`…)\n\t * is passed through unchanged. Returns `null` when no colour is set so the\n\t * inline binding is dropped and the `--hub-progress-accent` token (default or\n\t * theme override) takes over.\n\t */\n\tprotected readonly accentVar = computed(() => resolveHubAccent(this.color()));\n\n\t/** Host modifier classes derived from the signal inputs. */\n\tprotected readonly hostClasses = computed(() =>\n\t\t['hub-progress', `hub-progress--${this.size()}`, this.indeterminate() ? 'hub-progress--indeterminate' : '']\n\t\t\t.filter(Boolean)\n\t\t\t.join(' ')\n\t);\n}\n","@if (label()) {\n\t<span class=\"hub-progress__label\">{{ label() }}</span>\n}\n\n<span class=\"hub-progress__track\">\n\t<span class=\"hub-progress__indicator\"></span>\n</span>\n\n@if (showValue() && !indeterminate()) {\n\t<span class=\"hub-progress__value\">{{ displayValue() }}</span>\n}\n","import { ChangeDetectionStrategy, Component, computed, input, ViewEncapsulation } from '@angular/core';\nimport { HubMeterBand, HubMeterResolved } from './meter.types';\n\n/**\n * Graded gauge bar following HTML `<meter>` semantics for a scalar measurement\n * within a known range (disk usage, score, capacity…).\n *\n * The filled colour reflects which band the value falls in relative to the\n * `optimum` point: inside the `[low, high]` plateau it uses the optimum fill,\n * below `optimum` and outside the plateau the low fill, above it the high fill.\n * Every band colour derives from a `--hub-meter-*-bg` token so no colour is\n * hard-coded.\n */\n@Component({\n\tselector: 'hub-meter',\n\tstandalone: true,\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\ttemplateUrl: './meter.component.html',\n\tstyleUrl: './meter.component.scss',\n\tencapsulation: ViewEncapsulation.None,\n\thost: {\n\t\tclass: 'hub-meter',\n\t\trole: 'meter',\n\t\t'[class]': 'hostClasses()',\n\t\t'[attr.data-band]': 'band()',\n\t\t'[style.--hub-meter-value]': 'valueCss()',\n\t\t'[attr.aria-valuemin]': 'resolved().min',\n\t\t'[attr.aria-valuemax]': 'resolved().max',\n\t\t'[attr.aria-valuenow]': 'resolved().value',\n\t\t'[attr.aria-valuetext]': 'valueText()'\n\t}\n})\nexport class HubMeterComponent {\n\t/** Current measurement, expressed on the `[min, max]` scale. */\n\treadonly value = input<number>(0);\n\n\t/** Lower bound of the scale. */\n\treadonly min = input<number>(0);\n\n\t/** Upper bound of the scale. */\n\treadonly max = input<number>(1);\n\n\t/** Upper limit of the \"low\" region; defaults to {@link min} when omitted. */\n\treadonly low = input<number | undefined>(undefined);\n\n\t/** Lower limit of the \"high\" region; defaults to {@link max} when omitted. */\n\treadonly high = input<number | undefined>(undefined);\n\n\t/** Preferred point of the range; defaults to the midpoint when omitted. */\n\treadonly optimum = input<number | undefined>(undefined);\n\n\t/** Fully resolved, clamped bounds derived from the raw inputs. */\n\tprotected readonly resolved = computed<HubMeterResolved>(() => {\n\t\tconst min = this.min();\n\t\tconst max = Math.max(min, this.max());\n\t\tconst clamp = (candidate: number): number => Math.min(max, Math.max(min, candidate));\n\n\t\tconst value = clamp(this.value());\n\t\tconst low = this.low() === undefined ? min : clamp(this.low() as number);\n\t\tconst high = this.high() === undefined ? max : Math.max(low, clamp(this.high() as number));\n\t\tconst optimum = this.optimum() === undefined ? (min + max) / 2 : clamp(this.optimum() as number);\n\t\tconst span = max - min;\n\t\tconst percent = span <= 0 ? 0 : ((value - min) / span) * 100;\n\n\t\treturn { min, max, value, low, high, optimum, percent };\n\t});\n\n\t/**\n\t * The band the value falls in: `optimum` inside the `[low, high]` plateau,\n\t * otherwise `low`/`high` depending on which side of `optimum` the value sits.\n\t */\n\tprotected readonly band = computed<HubMeterBand>(() => {\n\t\tconst { value, low, high, optimum } = this.resolved();\n\t\tif (value >= low && value <= high) {\n\t\t\treturn 'optimum';\n\t\t}\n\t\treturn value < optimum ? 'low' : 'high';\n\t});\n\n\t/** Fill width as a CSS length consumed by `--hub-meter-value`. */\n\tprotected readonly valueCss = computed(() => `${this.resolved().percent}%`);\n\n\t/** Human-readable `aria-valuetext` (rounded percentage plus band). */\n\tprotected readonly valueText = computed(() => `${Math.round(this.resolved().percent)}% (${this.band()})`);\n\n\t/** Host modifier classes derived from the computed band. */\n\tprotected readonly hostClasses = computed(() => ['hub-meter', `hub-meter--${this.band()}`].join(' '));\n}\n","<span class=\"hub-meter__track\">\n\t<span class=\"hub-meter__indicator\"></span>\n</span>\n","import { booleanAttribute, ChangeDetectionStrategy, Component, computed, input, ViewEncapsulation } from '@angular/core';\nimport { HubRingBand, HubRingThresholds } from './ring.types';\n\n/**\n * Circular gauge (ring) rendering a normalized score as an SVG arc.\n *\n * The value is normalized against `max` (accepting either a `0..1` ratio or a\n * `0..max` figure) and drawn with a `stroke-dasharray`/`stroke-dashoffset` arc\n * over a `pathLength`-normalized circle, so the offset maps linearly to the\n * completion (100 at 0%, 0 at 100%). Optional {@link thresholds} recolour the\n * indicator through the `--hub-ring-low-color` / `--hub-ring-high-color` tokens;\n * a centred caption is projected through `<ng-content>`.\n *\n * @example\n * ```html\n * <hub-ring [value]=\"0.82\" [thresholds]=\"{ low: 0.4, high: 0.75 }\">Score</hub-ring>\n * ```\n */\n@Component({\n\tselector: 'hub-ring',\n\texportAs: 'hubGauge',\n\tstandalone: true,\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\ttemplateUrl: './ring.component.html',\n\tstyleUrl: './ring.component.scss',\n\tencapsulation: ViewEncapsulation.None,\n\thost: {\n\t\tclass: 'hub-ring',\n\t\trole: 'meter',\n\t\t'[class]': 'hostClasses()',\n\t\t'[style.--hub-ring-size]': 'sizeCss()',\n\t\t'[style.--hub-ring-thickness]': 'thicknessCss()',\n\t\t'[attr.aria-valuemin]': '0',\n\t\t'[attr.aria-valuemax]': 'max()',\n\t\t'[attr.aria-valuenow]': 'value()',\n\t\t'[attr.aria-valuetext]': 'displayValue()',\n\t\t'[attr.aria-label]': 'displayValue()'\n\t}\n})\nexport class HubRingComponent {\n\t/** Score value, expressed as a `0..1` ratio or a `0..max` figure. */\n\treadonly value = input<number>(0);\n\n\t/** Upper bound the {@link value} is normalized against. */\n\treadonly max = input<number>(1);\n\n\t/** Outer diameter of the ring (number → px, or any CSS length string). */\n\treadonly size = input<number | string>('4rem');\n\n\t/** Stroke width of the ring (number → px, or any CSS length string). */\n\treadonly thickness = input<number | string>('0.5rem');\n\n\t/** Optional colour thresholds recolouring the indicator by band. */\n\treadonly thresholds = input<HubRingThresholds | undefined>(undefined);\n\n\t/** Whether the rounded percentage is rendered in the centre. */\n\treadonly showValue = input(true, { transform: booleanAttribute });\n\n\t/** Total normalized path length of the SVG circle (see `pathLength`). */\n\tprotected readonly PATH_LENGTH = 100;\n\n\t/** Completion ratio in `[0, 1]`; `0` when `max` is not positive. */\n\tprotected readonly fraction = computed(() => {\n\t\tconst max = this.max();\n\t\tif (max <= 0) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn Math.min(1, Math.max(0, this.value() / max));\n\t});\n\n\t/** Completion as a `0..100` percentage. */\n\tprotected readonly percent = computed(() => this.fraction() * 100);\n\n\t/** Dash pattern for the indicator (the full normalized path). */\n\tprotected readonly dashArray = computed(() => this.PATH_LENGTH);\n\n\t/** Dash offset drawing the arc: `PATH_LENGTH` at 0%, `0` at 100%. */\n\tprotected readonly dashOffset = computed(() => this.PATH_LENGTH * (1 - this.fraction()));\n\n\t/** Rounded percentage label shown in the centre when {@link showValue} is on. */\n\tprotected readonly displayValue = computed(() => `${Math.round(this.percent())}%`);\n\n\t/** Colour band derived from the optional {@link thresholds} against the value. */\n\tprotected readonly band = computed<HubRingBand>(() => {\n\t\tconst thresholds = this.thresholds();\n\t\tif (!thresholds) {\n\t\t\treturn 'default';\n\t\t}\n\t\tconst value = this.value();\n\t\tif (thresholds.low !== undefined && value < thresholds.low) {\n\t\t\treturn 'low';\n\t\t}\n\t\tif (thresholds.high !== undefined && value >= thresholds.high) {\n\t\t\treturn 'high';\n\t\t}\n\t\treturn 'default';\n\t});\n\n\t/** Host modifier classes derived from the computed band. */\n\tprotected readonly hostClasses = computed(() => ['hub-ring', `hub-ring--${this.band()}`].join(' '));\n\n\t/** Ring diameter as a CSS length (numbers are treated as pixels). */\n\tprotected readonly sizeCss = computed(() => this.toCssLength(this.size()));\n\n\t/** Stroke thickness as a CSS length (numbers are treated as pixels). */\n\tprotected readonly thicknessCss = computed(() => this.toCssLength(this.thickness()));\n\n\t/** Normalizes a number|string dimension into a CSS length string. */\n\tprivate toCssLength(dimension: number | string): string {\n\t\treturn typeof dimension === 'number' ? `${dimension}px` : dimension;\n\t}\n}\n","<svg class=\"hub-ring__svg\" viewBox=\"0 0 100 100\" aria-hidden=\"true\" focusable=\"false\">\n\t<circle class=\"hub-ring__track\" cx=\"50\" cy=\"50\" r=\"44\" [attr.pathLength]=\"PATH_LENGTH\" />\n\t<circle\n\t\tclass=\"hub-ring__indicator\"\n\t\tcx=\"50\"\n\t\tcy=\"50\"\n\t\tr=\"44\"\n\t\t[attr.pathLength]=\"PATH_LENGTH\"\n\t\t[attr.stroke-dasharray]=\"dashArray()\"\n\t\t[attr.stroke-dashoffset]=\"dashOffset()\"\n\t/>\n</svg>\n\n<span class=\"hub-ring__caption\">\n\t@if (showValue()) {\n\t\t<span class=\"hub-ring__value\">{{ displayValue() }}</span>\n\t}\n\t<ng-content />\n</span>\n","/*\n * Public API Surface of ng-hub-ui-metrics\n */\n\n// Components\nexport { HubProgressComponent } from './lib/components/progress/progress.component';\nexport { HubMeterComponent } from './lib/components/meter/meter.component';\nexport { HubRingComponent } from './lib/components/ring/ring.component';\n\n// Types\nexport type { HubMetricsColor, HubMetricsBuiltinColor } from './lib/models/metrics.types';\nexport type { HubProgressSize } from './lib/components/progress/progress.types';\nexport type { HubMeterBand, HubMeterResolved } from './lib/components/meter/meter.types';\nexport type { HubRingBand, HubRingThresholds } from './lib/components/ring/ring.types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ng-hub-ui-metrics.mjs","sources":["../../../projects/metrics/src/lib/components/progress/progress.component.ts","../../../projects/metrics/src/lib/components/progress/progress.component.html","../../../projects/metrics/src/lib/components/meter/meter.component.ts","../../../projects/metrics/src/lib/components/meter/meter.component.html","../../../projects/metrics/src/lib/components/ring/ring.component.ts","../../../projects/metrics/src/lib/components/ring/ring.component.html","../../../projects/metrics/src/public-api.ts","../../../projects/metrics/src/ng-hub-ui-metrics.ts"],"sourcesContent":["import { booleanAttribute, ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { HubMetricsColor } from '../../models/metrics.types';\nimport { resolveHubAccent } from 'ng-hub-ui-utils';\nimport { HubProgressSize } from './progress.types';\n\n/**\n * Linear progress bar for a determinate or indeterminate read-only value.\n *\n * The component owns no hard-coded colour values: the semantic `color` input\n * maps to the host application's `--hub-sys-color-*` token family and drives the\n * indicator through the local `--hub-progress-accent` slot. When\n * {@link indeterminate} is set the bar loops a looping animation and drops\n * `aria-valuenow`, matching the ARIA `progressbar` contract for an unknown value.\n */\n@Component({\n\tselector: 'hub-progress',\n\tstandalone: true,\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\ttemplateUrl: './progress.component.html',\n\tstyleUrl: './progress.component.scss',\n\thost: {\n\t\tclass: 'hub-progress',\n\t\trole: 'progressbar',\n\t\t'[class]': 'hostClasses()',\n\t\t'[style.--hub-progress-accent]': 'accentVar()',\n\t\t'[style.--hub-progress-value]': 'valueCss()',\n\t\t'[attr.aria-valuemin]': '0',\n\t\t'[attr.aria-valuemax]': 'max()',\n\t\t'[attr.aria-valuenow]': 'ariaValueNow()',\n\t\t'[attr.aria-label]': 'label() || null',\n\t\t'[attr.aria-busy]': 'indeterminate() || null'\n\t}\n})\nexport class HubProgressComponent {\n\t/** Current progress value, expressed on the `[0, max]` scale. */\n\treadonly value = input<number>(0);\n\n\t/** Upper bound of the progress scale. */\n\treadonly max = input<number>(100);\n\n\t/** Renders a looping bar for work of unknown duration; drops `aria-valuenow`. */\n\treadonly indeterminate = input(false, { transform: booleanAttribute });\n\n\t/**\n\t * Optional accent driving the indicator colour. Accepts any colour: a bareword\n\t * (semantic name, registered accent or CSS named colour) maps to\n\t * `--hub-sys-color-<color>` with the word itself as the raw fallback, while a\n\t * literal (`#hex`, `rgb()`, `oklch()`, `var()`…) is passed through unchanged.\n\t * The resolved value is applied inline (a per-instance override); when omitted\n\t * the accent falls back to the `--hub-progress-accent` token, so a theme\n\t * (`hub-metrics-theme` mixin / ancestor override) can drive it instead.\n\t */\n\treadonly color = input<HubMetricsColor | undefined>(undefined);\n\n\t/** Size token controlling the bar thickness. */\n\treadonly size = input<HubProgressSize>('md');\n\n\t/** Whether the rounded percentage is rendered next to the bar. */\n\treadonly showValue = input(false, { transform: booleanAttribute });\n\n\t/** Accessible label describing what the progress bar measures. */\n\treadonly label = input<string>('');\n\n\t/** Value clamped to the `[0, max]` range; `0` when `max` is not positive. */\n\tprotected readonly clampedValue = computed(() => {\n\t\tconst max = this.max();\n\t\tif (max <= 0) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn Math.min(max, Math.max(0, this.value()));\n\t});\n\n\t/** Completion ratio as a `0..100` percentage; `0` while indeterminate. */\n\tprotected readonly percent = computed(() => {\n\t\tconst max = this.max();\n\t\tif (this.indeterminate() || max <= 0) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn (this.clampedValue() / max) * 100;\n\t});\n\n\t/** Indicator width as a CSS length consumed by `--hub-progress-value`. */\n\tprotected readonly valueCss = computed(() => `${this.percent()}%`);\n\n\t/** Rounded percentage label shown when {@link showValue} is enabled. */\n\tprotected readonly displayValue = computed(() => `${Math.round(this.percent())}%`);\n\n\t/** `aria-valuenow`, omitted (null) while the bar is {@link indeterminate}. */\n\tprotected readonly ariaValueNow = computed(() => (this.indeterminate() ? null : this.clampedValue()));\n\n\t/**\n\t * Local accent slot resolved from the {@link color} input. A bareword resolves\n\t * to `var(--hub-sys-color-<color>, <color>)` — the ds token with the word as a\n\t * raw fallback — while a literal colour (`#hex`, `rgb()`, `oklch()`, `var()`…)\n\t * is passed through unchanged. Returns `null` when no colour is set so the\n\t * inline binding is dropped and the `--hub-progress-accent` token (default or\n\t * theme override) takes over.\n\t */\n\tprotected readonly accentVar = computed(() => resolveHubAccent(this.color()));\n\n\t/** Host modifier classes derived from the signal inputs. */\n\tprotected readonly hostClasses = computed(() =>\n\t\t['hub-progress', `hub-progress--${this.size()}`, this.indeterminate() ? 'hub-progress--indeterminate' : '']\n\t\t\t.filter(Boolean)\n\t\t\t.join(' ')\n\t);\n}\n","@if (label()) {\n\t<span class=\"hub-progress__label\">{{ label() }}</span>\n}\n\n<span class=\"hub-progress__track\">\n\t<span class=\"hub-progress__indicator\"></span>\n</span>\n\n@if (showValue() && !indeterminate()) {\n\t<span class=\"hub-progress__value\">{{ displayValue() }}</span>\n}\n","import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { HubMeterBand, HubMeterResolved } from './meter.types';\n\n/**\n * Graded gauge bar following HTML `<meter>` semantics for a scalar measurement\n * within a known range (disk usage, score, capacity…).\n *\n * The filled colour reflects which band the value falls in relative to the\n * `optimum` point: inside the `[low, high]` plateau it uses the optimum fill,\n * below `optimum` and outside the plateau the low fill, above it the high fill.\n * Every band colour derives from a `--hub-meter-*-bg` token so no colour is\n * hard-coded.\n */\n@Component({\n\tselector: 'hub-meter',\n\tstandalone: true,\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\ttemplateUrl: './meter.component.html',\n\tstyleUrl: './meter.component.scss',\n\thost: {\n\t\tclass: 'hub-meter',\n\t\trole: 'meter',\n\t\t'[class]': 'hostClasses()',\n\t\t'[attr.data-band]': 'band()',\n\t\t'[style.--hub-meter-value]': 'valueCss()',\n\t\t'[attr.aria-valuemin]': 'resolved().min',\n\t\t'[attr.aria-valuemax]': 'resolved().max',\n\t\t'[attr.aria-valuenow]': 'resolved().value',\n\t\t'[attr.aria-valuetext]': 'valueText()',\n\t\t'[attr.aria-label]': 'label() || null'\n\t}\n})\nexport class HubMeterComponent {\n\t/** Current measurement, expressed on the `[min, max]` scale. */\n\treadonly value = input<number>(0);\n\n\t/** Lower bound of the scale. */\n\treadonly min = input<number>(0);\n\n\t/** Upper bound of the scale. */\n\treadonly max = input<number>(1);\n\n\t/** Upper limit of the \"low\" region; defaults to {@link min} when omitted. */\n\treadonly low = input<number | undefined>(undefined);\n\n\t/** Lower limit of the \"high\" region; defaults to {@link max} when omitted. */\n\treadonly high = input<number | undefined>(undefined);\n\n\t/** Preferred point of the range; defaults to the midpoint when omitted. */\n\treadonly optimum = input<number | undefined>(undefined);\n\n\t/**\n\t * Accessible label describing what is being measured. `role=\"meter\"` takes its\n\t * name from the author only, so without it the gauge reaches assistive\n\t * technology unnamed — the component renders no text of its own.\n\t */\n\treadonly label = input<string>('');\n\n\t/** Fully resolved, clamped bounds derived from the raw inputs. */\n\tprotected readonly resolved = computed<HubMeterResolved>(() => {\n\t\tconst min = this.min();\n\t\tconst max = Math.max(min, this.max());\n\t\tconst clamp = (candidate: number): number => Math.min(max, Math.max(min, candidate));\n\n\t\tconst value = clamp(this.value());\n\t\tconst low = this.low() === undefined ? min : clamp(this.low() as number);\n\t\tconst high = this.high() === undefined ? max : Math.max(low, clamp(this.high() as number));\n\t\tconst optimum = this.optimum() === undefined ? (min + max) / 2 : clamp(this.optimum() as number);\n\t\tconst span = max - min;\n\t\tconst percent = span <= 0 ? 0 : ((value - min) / span) * 100;\n\n\t\treturn { min, max, value, low, high, optimum, percent };\n\t});\n\n\t/**\n\t * The band the value falls in: `optimum` inside the `[low, high]` plateau,\n\t * otherwise `low`/`high` depending on which side of `optimum` the value sits.\n\t */\n\tprotected readonly band = computed<HubMeterBand>(() => {\n\t\tconst { value, low, high, optimum } = this.resolved();\n\t\tif (value >= low && value <= high) {\n\t\t\treturn 'optimum';\n\t\t}\n\t\treturn value < optimum ? 'low' : 'high';\n\t});\n\n\t/** Fill width as a CSS length consumed by `--hub-meter-value`. */\n\tprotected readonly valueCss = computed(() => `${this.resolved().percent}%`);\n\n\t/** Human-readable `aria-valuetext` (rounded percentage plus band). */\n\tprotected readonly valueText = computed(() => `${Math.round(this.resolved().percent)}% (${this.band()})`);\n\n\t/** Host modifier classes derived from the computed band. */\n\tprotected readonly hostClasses = computed(() => ['hub-meter', `hub-meter--${this.band()}`].join(' '));\n}\n","<span class=\"hub-meter__track\">\n\t<span class=\"hub-meter__indicator\"></span>\n</span>\n","import { booleanAttribute, ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport { HubRingBand, HubRingThresholds } from './ring.types';\n\n/**\n * Circular gauge (ring) rendering a normalized score as an SVG arc.\n *\n * The value is normalized against `max` (accepting either a `0..1` ratio or a\n * `0..max` figure) and drawn with a `stroke-dasharray`/`stroke-dashoffset` arc\n * over a `pathLength`-normalized circle, so the offset maps linearly to the\n * completion (100 at 0%, 0 at 100%). Optional {@link thresholds} recolour the\n * indicator through the `--hub-ring-low-color` / `--hub-ring-high-color` tokens;\n * a centred caption is projected through `<ng-content>`. That caption is decoration:\n * `role=\"meter\"` is named by the author, so pass {@link label} to name the gauge.\n *\n * @example\n * ```html\n * <hub-ring [value]=\"0.82\" [thresholds]=\"{ low: 0.4, high: 0.75 }\" label=\"Quality score\">Score</hub-ring>\n * ```\n */\n@Component({\n\tselector: 'hub-ring',\n\texportAs: 'hubGauge',\n\tstandalone: true,\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\ttemplateUrl: './ring.component.html',\n\tstyleUrl: './ring.component.scss',\n\thost: {\n\t\tclass: 'hub-ring',\n\t\trole: 'meter',\n\t\t'[class]': 'hostClasses()',\n\t\t'[style.--hub-ring-size]': 'sizeCss()',\n\t\t'[style.--hub-ring-thickness]': 'thicknessCss()',\n\t\t'[attr.aria-valuemin]': '0',\n\t\t'[attr.aria-valuemax]': 'max()',\n\t\t'[attr.aria-valuenow]': 'value()',\n\t\t'[attr.aria-valuetext]': 'displayValue()',\n\t\t'[attr.aria-label]': 'label() || null'\n\t}\n})\nexport class HubRingComponent {\n\t/** Score value, expressed as a `0..1` ratio or a `0..max` figure. */\n\treadonly value = input<number>(0);\n\n\t/** Upper bound the {@link value} is normalized against. */\n\treadonly max = input<number>(1);\n\n\t/**\n\t * Outer diameter of the ring (number → px, or any CSS length string), applied\n\t * inline as a per-instance override. Omit it to let the `--hub-ring-size`\n\t * token (default or theme override) drive the diameter.\n\t */\n\treadonly size = input<number | string | undefined>(undefined);\n\n\t/**\n\t * Stroke width of the ring (number → px, or any CSS length string), applied\n\t * inline as a per-instance override. Omit it to let the\n\t * `--hub-ring-thickness` token (default or theme override) drive the stroke.\n\t */\n\treadonly thickness = input<number | string | undefined>(undefined);\n\n\t/** Optional colour thresholds recolouring the indicator by band. */\n\treadonly thresholds = input<HubRingThresholds | undefined>(undefined);\n\n\t/** Whether the rounded percentage is rendered in the centre. */\n\treadonly showValue = input(true, { transform: booleanAttribute });\n\n\t/**\n\t * Accessible label describing what the ring scores. `role=\"meter\"` takes its\n\t * name from the author only, so neither the projected caption nor the centred\n\t * percentage can name the gauge — this input is the only way to do it.\n\t */\n\treadonly label = input<string>('');\n\n\t/** Total normalized path length of the SVG circle (see `pathLength`). */\n\tprotected readonly PATH_LENGTH = 100;\n\n\t/** Completion ratio in `[0, 1]`; `0` when `max` is not positive. */\n\tprotected readonly fraction = computed(() => {\n\t\tconst max = this.max();\n\t\tif (max <= 0) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn Math.min(1, Math.max(0, this.value() / max));\n\t});\n\n\t/** Completion as a `0..100` percentage. */\n\tprotected readonly percent = computed(() => this.fraction() * 100);\n\n\t/** Dash pattern for the indicator (the full normalized path). */\n\tprotected readonly dashArray = computed(() => this.PATH_LENGTH);\n\n\t/** Dash offset drawing the arc: `PATH_LENGTH` at 0%, `0` at 100%. */\n\tprotected readonly dashOffset = computed(() => this.PATH_LENGTH * (1 - this.fraction()));\n\n\t/** Rounded percentage label shown in the centre when {@link showValue} is on. */\n\tprotected readonly displayValue = computed(() => `${Math.round(this.percent())}%`);\n\n\t/** Colour band derived from the optional {@link thresholds} against the value. */\n\tprotected readonly band = computed<HubRingBand>(() => {\n\t\tconst thresholds = this.thresholds();\n\t\tif (!thresholds) {\n\t\t\treturn 'default';\n\t\t}\n\t\tconst value = this.value();\n\t\tif (thresholds.low !== undefined && value < thresholds.low) {\n\t\t\treturn 'low';\n\t\t}\n\t\tif (thresholds.high !== undefined && value >= thresholds.high) {\n\t\t\treturn 'high';\n\t\t}\n\t\treturn 'default';\n\t});\n\n\t/** Host modifier classes derived from the computed band. */\n\tprotected readonly hostClasses = computed(() => ['hub-ring', `hub-ring--${this.band()}`].join(' '));\n\n\t/** Ring diameter as a CSS length (numbers are treated as pixels). */\n\tprotected readonly sizeCss = computed(() => this.toCssLength(this.size()));\n\n\t/** Stroke thickness as a CSS length (numbers are treated as pixels). */\n\tprotected readonly thicknessCss = computed(() => this.toCssLength(this.thickness()));\n\n\t/**\n\t * Normalizes a number|string dimension into a CSS length string, returning\n\t * `null` when the input is unset so the inline binding is dropped altogether\n\t * and the stylesheet token wins the cascade instead of being shadowed.\n\t */\n\tprivate toCssLength(dimension: number | string | undefined): string | null {\n\t\tif (dimension === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\treturn typeof dimension === 'number' ? `${dimension}px` : dimension;\n\t}\n}\n","<svg class=\"hub-ring__svg\" viewBox=\"0 0 100 100\" aria-hidden=\"true\" focusable=\"false\">\n\t<circle class=\"hub-ring__track\" cx=\"50\" cy=\"50\" r=\"44\" [attr.pathLength]=\"PATH_LENGTH\" />\n\t<circle\n\t\tclass=\"hub-ring__indicator\"\n\t\tcx=\"50\"\n\t\tcy=\"50\"\n\t\tr=\"44\"\n\t\t[attr.pathLength]=\"PATH_LENGTH\"\n\t\t[attr.stroke-dasharray]=\"dashArray()\"\n\t\t[attr.stroke-dashoffset]=\"dashOffset()\"\n\t/>\n</svg>\n\n<span class=\"hub-ring__caption\">\n\t@if (showValue()) {\n\t\t<span class=\"hub-ring__value\">{{ displayValue() }}</span>\n\t}\n\t<ng-content />\n</span>\n","/*\n * Public API Surface of ng-hub-ui-metrics\n */\n\n// Components\nexport { HubProgressComponent } from './lib/components/progress/progress.component';\nexport { HubMeterComponent } from './lib/components/meter/meter.component';\nexport { HubRingComponent } from './lib/components/ring/ring.component';\n\n// Types\nexport type { HubMetricsColor, HubMetricsBuiltinColor } from './lib/models/metrics.types';\nexport type { HubProgressSize } from './lib/components/progress/progress.types';\nexport type { HubMeterBand, HubMeterResolved } from './lib/components/meter/meter.types';\nexport type { HubRingBand, HubRingThresholds } from './lib/components/ring/ring.types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-hub-ui-metrics",
3
- "version": "22.2.4",
3
+ "version": "22.4.0",
4
4
  "license": "MIT",
5
5
  "description": "Read-only value visualizations (progress, meter, gauge/ring) for Angular 21+, part of the ng-hub-ui family.",
6
6
  "author": "Carlos Morcillo <carlos.morcillo@me.com> (https://www.carlosmorcillo.com)",
@@ -28,8 +28,14 @@
28
28
  "peerDependencies": {
29
29
  "@angular/common": ">=21.0.0",
30
30
  "@angular/core": ">=21.0.0",
31
+ "ng-hub-ui-ds": ">=22.0.0",
31
32
  "ng-hub-ui-utils": ">=22.7.0"
32
33
  },
34
+ "peerDependenciesMeta": {
35
+ "ng-hub-ui-ds": {
36
+ "optional": true
37
+ }
38
+ },
33
39
  "dependencies": {
34
40
  "tslib": "^2.3.0"
35
41
  },
@@ -133,6 +133,12 @@ declare class HubMeterComponent {
133
133
  readonly high: _angular_core.InputSignal<number | undefined>;
134
134
  /** Preferred point of the range; defaults to the midpoint when omitted. */
135
135
  readonly optimum: _angular_core.InputSignal<number | undefined>;
136
+ /**
137
+ * Accessible label describing what is being measured. `role="meter"` takes its
138
+ * name from the author only, so without it the gauge reaches assistive
139
+ * technology unnamed — the component renders no text of its own.
140
+ */
141
+ readonly label: _angular_core.InputSignal<string>;
136
142
  /** Fully resolved, clamped bounds derived from the raw inputs. */
137
143
  protected readonly resolved: _angular_core.Signal<HubMeterResolved>;
138
144
  /**
@@ -147,7 +153,7 @@ declare class HubMeterComponent {
147
153
  /** Host modifier classes derived from the computed band. */
148
154
  protected readonly hostClasses: _angular_core.Signal<string>;
149
155
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<HubMeterComponent, never>;
150
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<HubMeterComponent, "hub-meter", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "low": { "alias": "low"; "required": false; "isSignal": true; }; "high": { "alias": "high"; "required": false; "isSignal": true; }; "optimum": { "alias": "optimum"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
156
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<HubMeterComponent, "hub-meter", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "low": { "alias": "low"; "required": false; "isSignal": true; }; "high": { "alias": "high"; "required": false; "isSignal": true; }; "optimum": { "alias": "optimum"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
151
157
  }
152
158
 
153
159
  /**
@@ -159,7 +165,7 @@ declare class HubMeterComponent {
159
165
  * keeps the neutral `--hub-ring-indicator` colour.
160
166
  */
161
167
  interface HubRingThresholds {
162
- /** Value at or below which the ring is treated as "low". */
168
+ /** Value strictly below which the ring is treated as "low"; `low` itself is not. */
163
169
  low?: number;
164
170
  /** Value at or above which the ring is treated as "high". */
165
171
  high?: number;
@@ -177,11 +183,12 @@ type HubRingBand = 'low' | 'default' | 'high';
177
183
  * over a `pathLength`-normalized circle, so the offset maps linearly to the
178
184
  * completion (100 at 0%, 0 at 100%). Optional {@link thresholds} recolour the
179
185
  * indicator through the `--hub-ring-low-color` / `--hub-ring-high-color` tokens;
180
- * a centred caption is projected through `<ng-content>`.
186
+ * a centred caption is projected through `<ng-content>`. That caption is decoration:
187
+ * `role="meter"` is named by the author, so pass {@link label} to name the gauge.
181
188
  *
182
189
  * @example
183
190
  * ```html
184
- * <hub-ring [value]="0.82" [thresholds]="{ low: 0.4, high: 0.75 }">Score</hub-ring>
191
+ * <hub-ring [value]="0.82" [thresholds]="{ low: 0.4, high: 0.75 }" label="Quality score">Score</hub-ring>
185
192
  * ```
186
193
  */
187
194
  declare class HubRingComponent {
@@ -189,14 +196,28 @@ declare class HubRingComponent {
189
196
  readonly value: _angular_core.InputSignal<number>;
190
197
  /** Upper bound the {@link value} is normalized against. */
191
198
  readonly max: _angular_core.InputSignal<number>;
192
- /** Outer diameter of the ring (number → px, or any CSS length string). */
193
- readonly size: _angular_core.InputSignal<string | number>;
194
- /** Stroke width of the ring (number → px, or any CSS length string). */
195
- readonly thickness: _angular_core.InputSignal<string | number>;
199
+ /**
200
+ * Outer diameter of the ring (number → px, or any CSS length string), applied
201
+ * inline as a per-instance override. Omit it to let the `--hub-ring-size`
202
+ * token (default or theme override) drive the diameter.
203
+ */
204
+ readonly size: _angular_core.InputSignal<string | number | undefined>;
205
+ /**
206
+ * Stroke width of the ring (number → px, or any CSS length string), applied
207
+ * inline as a per-instance override. Omit it to let the
208
+ * `--hub-ring-thickness` token (default or theme override) drive the stroke.
209
+ */
210
+ readonly thickness: _angular_core.InputSignal<string | number | undefined>;
196
211
  /** Optional colour thresholds recolouring the indicator by band. */
197
212
  readonly thresholds: _angular_core.InputSignal<HubRingThresholds | undefined>;
198
213
  /** Whether the rounded percentage is rendered in the centre. */
199
214
  readonly showValue: _angular_core.InputSignalWithTransform<boolean, unknown>;
215
+ /**
216
+ * Accessible label describing what the ring scores. `role="meter"` takes its
217
+ * name from the author only, so neither the projected caption nor the centred
218
+ * percentage can name the gauge — this input is the only way to do it.
219
+ */
220
+ readonly label: _angular_core.InputSignal<string>;
200
221
  /** Total normalized path length of the SVG circle (see `pathLength`). */
201
222
  protected readonly PATH_LENGTH = 100;
202
223
  /** Completion ratio in `[0, 1]`; `0` when `max` is not positive. */
@@ -214,13 +235,17 @@ declare class HubRingComponent {
214
235
  /** Host modifier classes derived from the computed band. */
215
236
  protected readonly hostClasses: _angular_core.Signal<string>;
216
237
  /** Ring diameter as a CSS length (numbers are treated as pixels). */
217
- protected readonly sizeCss: _angular_core.Signal<string>;
238
+ protected readonly sizeCss: _angular_core.Signal<string | null>;
218
239
  /** Stroke thickness as a CSS length (numbers are treated as pixels). */
219
- protected readonly thicknessCss: _angular_core.Signal<string>;
220
- /** Normalizes a number|string dimension into a CSS length string. */
240
+ protected readonly thicknessCss: _angular_core.Signal<string | null>;
241
+ /**
242
+ * Normalizes a number|string dimension into a CSS length string, returning
243
+ * `null` when the input is unset so the inline binding is dropped altogether
244
+ * and the stylesheet token wins the cascade instead of being shadowed.
245
+ */
221
246
  private toCssLength;
222
247
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<HubRingComponent, never>;
223
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<HubRingComponent, "hub-ring", ["hubGauge"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "thickness": { "alias": "thickness"; "required": false; "isSignal": true; }; "thresholds": { "alias": "thresholds"; "required": false; "isSignal": true; }; "showValue": { "alias": "showValue"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
248
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<HubRingComponent, "hub-ring", ["hubGauge"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "thickness": { "alias": "thickness"; "required": false; "isSignal": true; }; "thresholds": { "alias": "thresholds"; "required": false; "isSignal": true; }; "showValue": { "alias": "showValue"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
224
249
  }
225
250
 
226
251
  export { HubMeterComponent, HubProgressComponent, HubRingComponent };