@dragonworks/ngx-dashboard-widgets 20.0.5 → 20.1.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,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, computed, Component, input, numberAttribute, booleanAttribute, ElementRef, NgZone, PLATFORM_ID, DestroyRef, Directive, ChangeDetectionStrategy, Renderer2, viewChild, effect } from '@angular/core';
|
|
2
|
+
import { inject, signal, computed, Component, input, numberAttribute, booleanAttribute, ElementRef, NgZone, PLATFORM_ID, DestroyRef, Directive, ChangeDetectionStrategy, Renderer2, viewChild, effect, LOCALE_ID, afterNextRender } from '@angular/core';
|
|
3
3
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
4
4
|
import * as i2 from '@angular/material/dialog';
|
|
5
5
|
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule, MatDialog } from '@angular/material/dialog';
|
|
@@ -20,18 +20,20 @@ import * as i5$1 from '@angular/material/input';
|
|
|
20
20
|
import { MatInputModule } from '@angular/material/input';
|
|
21
21
|
import * as i3$1 from '@angular/material/radio';
|
|
22
22
|
import { MatRadioModule } from '@angular/material/radio';
|
|
23
|
+
import { from, map, of } from 'rxjs';
|
|
24
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
23
25
|
|
|
24
26
|
// arrow-widget.metadata.ts
|
|
25
|
-
const svgIcon$
|
|
27
|
+
const svgIcon$3 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M320-120v-320H120l360-440 360 440H640v320H320Zm80-80h160v-320h111L480-754 289-520h111v320Zm80-320Z"/></svg>';
|
|
26
28
|
|
|
27
29
|
class ArrowStateDialogComponent {
|
|
28
30
|
data = inject(MAT_DIALOG_DATA);
|
|
29
31
|
dialogRef = inject((MatDialogRef));
|
|
30
32
|
// State signals
|
|
31
|
-
direction = signal(this.data.direction);
|
|
32
|
-
opacity = signal(this.data.opacity ?? 1);
|
|
33
|
-
hasBackground = signal(this.data.hasBackground ?? true);
|
|
34
|
-
transparentBackground = signal(!(this.data.hasBackground ?? true));
|
|
33
|
+
direction = signal(this.data.direction, ...(ngDevMode ? [{ debugName: "direction" }] : []));
|
|
34
|
+
opacity = signal(this.data.opacity ?? 1, ...(ngDevMode ? [{ debugName: "opacity" }] : []));
|
|
35
|
+
hasBackground = signal(this.data.hasBackground ?? true, ...(ngDevMode ? [{ debugName: "hasBackground" }] : []));
|
|
36
|
+
transparentBackground = signal(!(this.data.hasBackground ?? true), ...(ngDevMode ? [{ debugName: "transparentBackground" }] : []));
|
|
35
37
|
// Store original values for comparison
|
|
36
38
|
originalDirection = this.data.direction;
|
|
37
39
|
originalOpacity = this.data.opacity ?? 1;
|
|
@@ -45,8 +47,8 @@ class ArrowStateDialogComponent {
|
|
|
45
47
|
left: 270,
|
|
46
48
|
};
|
|
47
49
|
return rotationMap[this.direction()];
|
|
48
|
-
});
|
|
49
|
-
rotationTransform = computed(() => `rotate(${this.rotation()}deg)
|
|
50
|
+
}, ...(ngDevMode ? [{ debugName: "rotation" }] : []));
|
|
51
|
+
rotationTransform = computed(() => `rotate(${this.rotation()}deg)`, ...(ngDevMode ? [{ debugName: "rotationTransform" }] : []));
|
|
50
52
|
directionName = computed(() => {
|
|
51
53
|
const nameMap = {
|
|
52
54
|
up: 'Up',
|
|
@@ -55,10 +57,10 @@ class ArrowStateDialogComponent {
|
|
|
55
57
|
left: 'Left',
|
|
56
58
|
};
|
|
57
59
|
return nameMap[this.direction()];
|
|
58
|
-
});
|
|
60
|
+
}, ...(ngDevMode ? [{ debugName: "directionName" }] : []));
|
|
59
61
|
hasChanged = computed(() => this.direction() !== this.originalDirection ||
|
|
60
62
|
this.opacity() !== this.originalOpacity ||
|
|
61
|
-
this.hasBackground() !== this.originalHasBackground);
|
|
63
|
+
this.hasBackground() !== this.originalHasBackground, ...(ngDevMode ? [{ debugName: "hasChanged" }] : []));
|
|
62
64
|
formatOpacity(value) {
|
|
63
65
|
return Math.round(value * 100);
|
|
64
66
|
}
|
|
@@ -76,8 +78,8 @@ class ArrowStateDialogComponent {
|
|
|
76
78
|
hasBackground: this.hasBackground(),
|
|
77
79
|
});
|
|
78
80
|
}
|
|
79
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.
|
|
80
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.
|
|
81
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ArrowStateDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
82
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.2.1", type: ArrowStateDialogComponent, isStandalone: true, selector: "lib-arrow-state-dialog", ngImport: i0, template: `
|
|
81
83
|
<h2 mat-dialog-title>Arrow Settings</h2>
|
|
82
84
|
<mat-dialog-content>
|
|
83
85
|
<!-- Direction Selection -->
|
|
@@ -121,7 +123,7 @@ class ArrowStateDialogComponent {
|
|
|
121
123
|
</mat-dialog-actions>
|
|
122
124
|
`, isInline: true, styles: ["mat-dialog-content{display:block;overflow-y:auto;overflow-x:hidden}mat-form-field{width:100%;display:block;margin-bottom:1rem}.direction-field{margin-top:1rem}.slider-field{margin-bottom:1.5rem;margin-right:1rem}.field-label{display:block;margin-bottom:.5rem}mat-slider{width:100%;display:block}.toggle-field{display:flex;align-items:center;gap:.75rem;margin-bottom:.5rem}.toggle-hint{margin:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: i6.MatSlider, selector: "mat-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "color", "disableRipple", "max", "step", "displayWith"], exportAs: ["matSlider"] }, { kind: "directive", type: i6.MatSliderThumb, selector: "input[matSliderThumb]", inputs: ["value"], outputs: ["valueChange", "dragStart", "dragEnd"], exportAs: ["matSliderThumb"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }] });
|
|
123
125
|
}
|
|
124
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ArrowStateDialogComponent, decorators: [{
|
|
125
127
|
type: Component,
|
|
126
128
|
args: [{ selector: 'lib-arrow-state-dialog', standalone: true, imports: [
|
|
127
129
|
CommonModule,
|
|
@@ -183,16 +185,16 @@ class ArrowWidgetComponent {
|
|
|
183
185
|
widgetTypeid: '@default/arrow-widget',
|
|
184
186
|
name: 'Arrow',
|
|
185
187
|
description: 'A generic arrow',
|
|
186
|
-
svgIcon: svgIcon$
|
|
188
|
+
svgIcon: svgIcon$3,
|
|
187
189
|
};
|
|
188
190
|
#sanitizer = inject(DomSanitizer);
|
|
189
191
|
#dialog = inject(MatDialog);
|
|
190
|
-
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon$
|
|
192
|
+
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon$3);
|
|
191
193
|
state = signal({
|
|
192
194
|
direction: 'up',
|
|
193
195
|
opacity: 0.3,
|
|
194
196
|
hasBackground: true,
|
|
195
|
-
});
|
|
197
|
+
}, ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
196
198
|
// Computed rotation
|
|
197
199
|
rotationAngle = computed(() => {
|
|
198
200
|
const rotationMap = {
|
|
@@ -202,7 +204,7 @@ class ArrowWidgetComponent {
|
|
|
202
204
|
left: 270,
|
|
203
205
|
};
|
|
204
206
|
return rotationMap[this.state().direction];
|
|
205
|
-
});
|
|
207
|
+
}, ...(ngDevMode ? [{ debugName: "rotationAngle" }] : []));
|
|
206
208
|
dashboardSetState(state) {
|
|
207
209
|
if (state) {
|
|
208
210
|
this.state.update((current) => ({
|
|
@@ -228,32 +230,32 @@ class ArrowWidgetComponent {
|
|
|
228
230
|
}
|
|
229
231
|
});
|
|
230
232
|
}
|
|
231
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.
|
|
232
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.
|
|
233
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ArrowWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
234
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.2.1", type: ArrowWidgetComponent, isStandalone: true, selector: "ngx-dashboard-arrow-widget", ngImport: i0, template: "<!-- arrow-widget.component.html -->\r\n<div class=\"svg-wrapper\" [class.has-background]=\"state().hasBackground\">\r\n <div\r\n class=\"svg-placeholder\"\r\n [innerHTML]=\"safeSvgIcon\"\r\n [style.transform]=\"'rotate(' + rotationAngle() + 'deg)'\"\r\n [style.opacity]=\"state().opacity\"\r\n ></div>\r\n</div>\r\n", styles: [":host{display:block;container-type:size;width:100%;height:100%;overflow:hidden}.svg-wrapper{display:flex;align-items:center;justify-content:center;height:100%;width:100%;box-sizing:border-box;transition:background-color var(--mat-sys-motion-duration-medium2) var(--mat-sys-motion-easing-standard)}.svg-wrapper.has-background{background-color:var(--mat-sys-surface-container-high);border-radius:4px}.svg-placeholder{width:min(80cqw,80cqh);aspect-ratio:1/1;opacity:.3;transition:transform .3s ease-in-out,opacity .3s ease,color .2s ease;transform-origin:center center;color:var(--mat-sys-on-surface-variant, #6c757d)}.has-background .svg-placeholder{color:var(--mat-sys-on-surface, #1f1f1f)}.svg-placeholder ::ng-deep svg{width:100%;height:100%;display:block}.svg-wrapper:hover .svg-placeholder{color:var(--mat-sys-primary, #6750a4)}\n"] });
|
|
233
235
|
}
|
|
234
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
236
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ArrowWidgetComponent, decorators: [{
|
|
235
237
|
type: Component,
|
|
236
|
-
args: [{ selector: 'ngx-dashboard-arrow-widget', imports: [], template: "<!-- arrow-widget.component.html -->\r\n<div class=\"svg-wrapper\" [class.has-background]=\"state().hasBackground\">\r\n <div\r\n class=\"svg-placeholder\"\r\n [innerHTML]=\"safeSvgIcon\"\r\n [style.transform]=\"'rotate(' + rotationAngle() + 'deg)'\"\r\n [style.opacity]=\"state().opacity\"\r\n ></div>\r\n</div>\r\n", styles: [":host{display:block;container-type:size;width:100%;height:100%;overflow:hidden}.svg-wrapper{display:flex;align-items:center;justify-content:center;height:100%;width:100%;box-sizing:border-box;transition:background-color var(--mat-sys-motion-duration-medium2) var(--mat-sys-motion-easing-standard)}.svg-wrapper.has-background{background-color:var(--mat-sys-surface-container-high);border-radius:4px}.svg-placeholder{width:min(80cqw,80cqh);aspect-ratio:1/1;opacity:.3;transition:transform .3s ease-in-out,opacity .3s ease;transform-origin:center center
|
|
238
|
+
args: [{ selector: 'ngx-dashboard-arrow-widget', imports: [], template: "<!-- arrow-widget.component.html -->\r\n<div class=\"svg-wrapper\" [class.has-background]=\"state().hasBackground\">\r\n <div\r\n class=\"svg-placeholder\"\r\n [innerHTML]=\"safeSvgIcon\"\r\n [style.transform]=\"'rotate(' + rotationAngle() + 'deg)'\"\r\n [style.opacity]=\"state().opacity\"\r\n ></div>\r\n</div>\r\n", styles: [":host{display:block;container-type:size;width:100%;height:100%;overflow:hidden}.svg-wrapper{display:flex;align-items:center;justify-content:center;height:100%;width:100%;box-sizing:border-box;transition:background-color var(--mat-sys-motion-duration-medium2) var(--mat-sys-motion-easing-standard)}.svg-wrapper.has-background{background-color:var(--mat-sys-surface-container-high);border-radius:4px}.svg-placeholder{width:min(80cqw,80cqh);aspect-ratio:1/1;opacity:.3;transition:transform .3s ease-in-out,opacity .3s ease,color .2s ease;transform-origin:center center;color:var(--mat-sys-on-surface-variant, #6c757d)}.has-background .svg-placeholder{color:var(--mat-sys-on-surface, #1f1f1f)}.svg-placeholder ::ng-deep svg{width:100%;height:100%;display:block}.svg-wrapper:hover .svg-placeholder{color:var(--mat-sys-primary, #6750a4)}\n"] }]
|
|
237
239
|
}] });
|
|
238
240
|
|
|
239
241
|
// label-widget.metadata.ts
|
|
240
|
-
const svgIcon$
|
|
242
|
+
const svgIcon$2 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M280-280h280v-80H280v80Zm0-160h400v-80H280v80Zm0-160h400v-80H280v80Zm-80 480q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"/></svg>';
|
|
241
243
|
|
|
242
244
|
class LabelStateDialogComponent {
|
|
243
245
|
data = inject(MAT_DIALOG_DATA);
|
|
244
246
|
dialogRef = inject((MatDialogRef));
|
|
245
247
|
// State signals
|
|
246
|
-
label = signal(this.data.label ?? '');
|
|
247
|
-
fontSize = signal(this.data.fontSize ?? 16);
|
|
248
|
-
alignment = signal(this.data.alignment ?? 'center');
|
|
249
|
-
fontWeight = signal(this.data.fontWeight ?? 'normal');
|
|
250
|
-
opacity = signal(this.data.opacity ?? 1);
|
|
251
|
-
hasBackground = signal(this.data.hasBackground ?? true);
|
|
252
|
-
transparentBackground = signal(!(this.data.hasBackground ?? true));
|
|
253
|
-
responsive = signal(this.data.responsive ?? false);
|
|
248
|
+
label = signal(this.data.label ?? '', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
249
|
+
fontSize = signal(this.data.fontSize ?? 16, ...(ngDevMode ? [{ debugName: "fontSize" }] : []));
|
|
250
|
+
alignment = signal(this.data.alignment ?? 'center', ...(ngDevMode ? [{ debugName: "alignment" }] : []));
|
|
251
|
+
fontWeight = signal(this.data.fontWeight ?? 'normal', ...(ngDevMode ? [{ debugName: "fontWeight" }] : []));
|
|
252
|
+
opacity = signal(this.data.opacity ?? 1, ...(ngDevMode ? [{ debugName: "opacity" }] : []));
|
|
253
|
+
hasBackground = signal(this.data.hasBackground ?? true, ...(ngDevMode ? [{ debugName: "hasBackground" }] : []));
|
|
254
|
+
transparentBackground = signal(!(this.data.hasBackground ?? true), ...(ngDevMode ? [{ debugName: "transparentBackground" }] : []));
|
|
255
|
+
responsive = signal(this.data.responsive ?? false, ...(ngDevMode ? [{ debugName: "responsive" }] : []));
|
|
254
256
|
// Responsive font size constraints
|
|
255
|
-
minFontSize = signal(this.data.minFontSize ?? 8);
|
|
256
|
-
maxFontSize = signal(this.data.maxFontSize ?? 64);
|
|
257
|
+
minFontSize = signal(this.data.minFontSize ?? 8, ...(ngDevMode ? [{ debugName: "minFontSize" }] : []));
|
|
258
|
+
maxFontSize = signal(this.data.maxFontSize ?? 64, ...(ngDevMode ? [{ debugName: "maxFontSize" }] : []));
|
|
257
259
|
// Store original values for comparison
|
|
258
260
|
originalLabel = this.data.label ?? '';
|
|
259
261
|
originalFontSize = this.data.fontSize ?? 16;
|
|
@@ -268,15 +270,15 @@ class LabelStateDialogComponent {
|
|
|
268
270
|
isMinFontSizeValid = computed(() => {
|
|
269
271
|
const min = this.minFontSize();
|
|
270
272
|
return min >= 8 && min <= 24;
|
|
271
|
-
});
|
|
273
|
+
}, ...(ngDevMode ? [{ debugName: "isMinFontSizeValid" }] : []));
|
|
272
274
|
isMaxFontSizeValid = computed(() => {
|
|
273
275
|
const max = this.maxFontSize();
|
|
274
276
|
return max >= 16 && max <= 128;
|
|
275
|
-
});
|
|
276
|
-
isFontSizeRangeValid = computed(() => this.minFontSize() < this.maxFontSize());
|
|
277
|
+
}, ...(ngDevMode ? [{ debugName: "isMaxFontSizeValid" }] : []));
|
|
278
|
+
isFontSizeRangeValid = computed(() => this.minFontSize() < this.maxFontSize(), ...(ngDevMode ? [{ debugName: "isFontSizeRangeValid" }] : []));
|
|
277
279
|
isFormValid = computed(() => this.isMinFontSizeValid() &&
|
|
278
280
|
this.isMaxFontSizeValid() &&
|
|
279
|
-
this.isFontSizeRangeValid());
|
|
281
|
+
this.isFontSizeRangeValid(), ...(ngDevMode ? [{ debugName: "isFormValid" }] : []));
|
|
280
282
|
// Computed values
|
|
281
283
|
hasChanged = computed(() => this.label() !== this.originalLabel ||
|
|
282
284
|
this.fontSize() !== this.originalFontSize ||
|
|
@@ -286,7 +288,7 @@ class LabelStateDialogComponent {
|
|
|
286
288
|
this.hasBackground() !== this.originalHasBackground ||
|
|
287
289
|
this.responsive() !== this.originalResponsive ||
|
|
288
290
|
this.minFontSize() !== this.originalMinFontSize ||
|
|
289
|
-
this.maxFontSize() !== this.originalMaxFontSize);
|
|
291
|
+
this.maxFontSize() !== this.originalMaxFontSize, ...(ngDevMode ? [{ debugName: "hasChanged" }] : []));
|
|
290
292
|
formatOpacity(value) {
|
|
291
293
|
return Math.round(value * 100);
|
|
292
294
|
}
|
|
@@ -334,8 +336,8 @@ class LabelStateDialogComponent {
|
|
|
334
336
|
maxFontSize: this.maxFontSize(),
|
|
335
337
|
});
|
|
336
338
|
}
|
|
337
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.
|
|
338
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.
|
|
339
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: LabelStateDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
340
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.1", type: LabelStateDialogComponent, isStandalone: true, selector: "lib-label-state-dialog", ngImport: i0, template: `
|
|
339
341
|
<h2 mat-dialog-title>Label Settings</h2>
|
|
340
342
|
<mat-dialog-content>
|
|
341
343
|
<mat-form-field appearance="outline" class="label-text-field">
|
|
@@ -487,7 +489,7 @@ class LabelStateDialogComponent {
|
|
|
487
489
|
</mat-dialog-actions>
|
|
488
490
|
`, isInline: true, styles: ["mat-dialog-content{display:block;overflow-y:auto;overflow-x:hidden}mat-form-field{width:100%;display:block;margin-bottom:1rem}.label-text-field{margin-top:1rem}.row-layout{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1rem}.row-layout mat-form-field{margin-bottom:0}.slider-section{margin-bottom:1.5rem;margin-right:1rem}.slider-label{display:block;margin-bottom:.5rem}mat-slider{width:100%;display:block}.toggle-section{display:flex;align-items:center;gap:.75rem;margin-bottom:1rem}.toggle-description{margin:0}.responsive-section{margin-bottom:1.5rem;padding:1rem;border-radius:12px;background-color:var(--mat-app-surface-variant, rgba(var(--mat-app-on-surface-rgb, 0, 0, 0), .05));border:1px solid var(--mat-app-outline-variant, rgba(var(--mat-app-on-surface-rgb, 0, 0, 0), .12))}.section-label{display:block;margin-bottom:.75rem;font-weight:500;color:var(--mat-app-on-surface-variant, rgba(var(--mat-app-on-surface-rgb, 0, 0, 0), .6))}.responsive-section .row-layout{margin-bottom:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: i6.MatSlider, selector: "mat-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "color", "disableRipple", "max", "step", "displayWith"], exportAs: ["matSlider"] }, { kind: "directive", type: i6.MatSliderThumb, selector: "input[matSliderThumb]", inputs: ["value"], outputs: ["valueChange", "dragStart", "dragEnd"], exportAs: ["matSliderThumb"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }] });
|
|
489
491
|
}
|
|
490
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
492
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: LabelStateDialogComponent, decorators: [{
|
|
491
493
|
type: Component,
|
|
492
494
|
args: [{ selector: 'lib-label-state-dialog', standalone: true, imports: [
|
|
493
495
|
CommonModule,
|
|
@@ -664,18 +666,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
664
666
|
class ResponsiveTextDirective {
|
|
665
667
|
/* ───────────────────────── Inputs with transforms ─────────────── */
|
|
666
668
|
/** Minimum font-size in pixels (accessibility floor) */
|
|
667
|
-
minFontSize = input(8, { transform: numberAttribute });
|
|
669
|
+
minFontSize = input(8, ...(ngDevMode ? [{ debugName: "minFontSize", transform: numberAttribute }] : [{ transform: numberAttribute }]));
|
|
668
670
|
/** Maximum font-size in pixels (layout ceiling) */
|
|
669
|
-
maxFontSize = input(512, { transform: numberAttribute });
|
|
671
|
+
maxFontSize = input(512, ...(ngDevMode ? [{ debugName: "maxFontSize", transform: numberAttribute }] : [{ transform: numberAttribute }]));
|
|
670
672
|
/**
|
|
671
673
|
* Line-height: pass a multiplier (e.g. 1.1) or absolute px value.
|
|
672
674
|
* For single-line text a multiplier < 10 is treated as unitless.
|
|
673
675
|
*/
|
|
674
|
-
lineHeight = input(1.1, { transform: numberAttribute });
|
|
676
|
+
lineHeight = input(1.1, ...(ngDevMode ? [{ debugName: "lineHeight", transform: numberAttribute }] : [{ transform: numberAttribute }]));
|
|
675
677
|
/** Whether to observe text mutations after first render */
|
|
676
|
-
observeMutations = input(true, { transform: booleanAttribute });
|
|
678
|
+
observeMutations = input(true, ...(ngDevMode ? [{ debugName: "observeMutations", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
677
679
|
/** Debounce delay in ms for resize/mutation callbacks */
|
|
678
|
-
debounceMs = input(16, { transform: numberAttribute });
|
|
680
|
+
debounceMs = input(16, ...(ngDevMode ? [{ debugName: "debounceMs", transform: numberAttribute }] : [{ transform: numberAttribute }]));
|
|
679
681
|
/* ───────────────────────── Private state ───────────────────────── */
|
|
680
682
|
el = inject(ElementRef);
|
|
681
683
|
zone = inject(NgZone);
|
|
@@ -701,7 +703,12 @@ class ResponsiveTextDirective {
|
|
|
701
703
|
lastMaxW = 0;
|
|
702
704
|
lastMaxH = 0;
|
|
703
705
|
lastFontSize = 0;
|
|
704
|
-
|
|
706
|
+
constructor() {
|
|
707
|
+
// Set up cleanup on component destruction using modern DestroyRef
|
|
708
|
+
this.destroyRef.onDestroy(() => {
|
|
709
|
+
this.cleanup();
|
|
710
|
+
});
|
|
711
|
+
}
|
|
705
712
|
ngAfterViewInit() {
|
|
706
713
|
if (!isPlatformBrowser(this.platformId))
|
|
707
714
|
return;
|
|
@@ -717,9 +724,6 @@ class ResponsiveTextDirective {
|
|
|
717
724
|
}
|
|
718
725
|
});
|
|
719
726
|
}
|
|
720
|
-
ngOnDestroy() {
|
|
721
|
-
this.cleanup();
|
|
722
|
-
}
|
|
723
727
|
/* ───────────────────── Core fitting logic ───────────────────── */
|
|
724
728
|
/**
|
|
725
729
|
* Debounced fit handler to prevent excessive recalculations
|
|
@@ -896,13 +900,13 @@ class ResponsiveTextDirective {
|
|
|
896
900
|
// Clear canvas context
|
|
897
901
|
this._ctx = undefined;
|
|
898
902
|
}
|
|
899
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.
|
|
900
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.
|
|
903
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ResponsiveTextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
904
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.2.1", type: ResponsiveTextDirective, isStandalone: true, selector: "[libResponsiveText]", inputs: { minFontSize: { classPropertyName: "minFontSize", publicName: "minFontSize", isSignal: true, isRequired: false, transformFunction: null }, maxFontSize: { classPropertyName: "maxFontSize", publicName: "maxFontSize", isSignal: true, isRequired: false, transformFunction: null }, lineHeight: { classPropertyName: "lineHeight", publicName: "lineHeight", isSignal: true, isRequired: false, transformFunction: null }, observeMutations: { classPropertyName: "observeMutations", publicName: "observeMutations", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.display": "\"block\"", "style.width": "\"100%\"", "style.white-space": "\"nowrap\"", "style.overflow": "\"visible\"" } }, ngImport: i0 });
|
|
901
905
|
}
|
|
902
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
906
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ResponsiveTextDirective, decorators: [{
|
|
903
907
|
type: Directive,
|
|
904
908
|
args: [{
|
|
905
|
-
selector: '[
|
|
909
|
+
selector: '[libResponsiveText]',
|
|
906
910
|
standalone: true,
|
|
907
911
|
host: {
|
|
908
912
|
'[style.display]': '"block"',
|
|
@@ -911,7 +915,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
911
915
|
'[style.overflow]': '"visible"',
|
|
912
916
|
},
|
|
913
917
|
}]
|
|
914
|
-
}] });
|
|
918
|
+
}], ctorParameters: () => [] });
|
|
915
919
|
|
|
916
920
|
// label-widget.component.ts
|
|
917
921
|
class LabelWidgetComponent {
|
|
@@ -919,11 +923,11 @@ class LabelWidgetComponent {
|
|
|
919
923
|
widgetTypeid: '@default/label-widget',
|
|
920
924
|
name: 'Label',
|
|
921
925
|
description: 'A generic text label',
|
|
922
|
-
svgIcon: svgIcon$
|
|
926
|
+
svgIcon: svgIcon$2,
|
|
923
927
|
};
|
|
924
928
|
#sanitizer = inject(DomSanitizer);
|
|
925
929
|
#dialog = inject(MatDialog);
|
|
926
|
-
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon$
|
|
930
|
+
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon$2);
|
|
927
931
|
state = signal({
|
|
928
932
|
label: '',
|
|
929
933
|
fontSize: 16,
|
|
@@ -934,7 +938,7 @@ class LabelWidgetComponent {
|
|
|
934
938
|
responsive: false,
|
|
935
939
|
minFontSize: 8, // Accessible minimum for responsive text
|
|
936
940
|
maxFontSize: 64, // Practical maximum for widget display
|
|
937
|
-
});
|
|
941
|
+
}, ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
938
942
|
dashboardSetState(state) {
|
|
939
943
|
if (state) {
|
|
940
944
|
this.state.update((current) => ({
|
|
@@ -969,17 +973,17 @@ class LabelWidgetComponent {
|
|
|
969
973
|
return this.state().label?.trim();
|
|
970
974
|
}
|
|
971
975
|
// Computed properties for responsive font size limits with fallbacks
|
|
972
|
-
minFontSize = computed(() => this.state().minFontSize ?? 8);
|
|
973
|
-
maxFontSize = computed(() => this.state().maxFontSize ?? 64);
|
|
974
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.
|
|
975
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.
|
|
976
|
+
minFontSize = computed(() => this.state().minFontSize ?? 8, ...(ngDevMode ? [{ debugName: "minFontSize" }] : []));
|
|
977
|
+
maxFontSize = computed(() => this.state().maxFontSize ?? 64, ...(ngDevMode ? [{ debugName: "maxFontSize" }] : []));
|
|
978
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: LabelWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
979
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.1", type: LabelWidgetComponent, isStandalone: true, selector: "ngx-dashboard-label-widget", ngImport: i0, template: "@if (hasContent) {\r\n<div\r\n class=\"label-widget\"\r\n [style.fontSize.rem]=\"state().responsive ? null : state().fontSize! / 16\"\r\n [style.--widget-opacity]=\"state().opacity\"\r\n [class.text-left]=\"state().alignment === 'left'\"\r\n [class.text-right]=\"state().alignment === 'right'\"\r\n [class.font-bold]=\"state().fontWeight === 'bold'\"\r\n [class.has-background]=\"state().hasBackground\"\r\n>\r\n @if (state().responsive) {\r\n <div class=\"label-text\" libResponsiveText [minFontSize]=\"minFontSize()\" [maxFontSize]=\"maxFontSize()\">{{ label }}</div>\r\n } @else {\r\n <div class=\"label-text\">{{ label }}</div>\r\n }\r\n</div>\r\n} @else {\r\n<div class=\"svg-wrapper\" [class.has-background]=\"state().hasBackground\">\r\n <div class=\"svg-placeholder\" [innerHTML]=\"safeSvgIcon\"></div>\r\n</div>\r\n}\r\n", styles: [":host{display:block;container-type:size;width:100%;height:100%;overflow:hidden}.svg-wrapper,.label-widget{display:flex;align-items:center;justify-content:center;height:100%;width:100%;box-sizing:border-box;transition:background-color var(--mat-sys-motion-duration-medium2) var(--mat-sys-motion-easing-standard)}.has-background.svg-wrapper,.has-background.label-widget{background-color:var(--mat-sys-surface-container-high);border-radius:4px}.label-widget{overflow:hidden;container-type:size;padding:var(--mat-sys-spacing-4);color:var(--mat-sys-on-surface-variant, #6c757d);opacity:var(--widget-opacity, 1)}.label-widget.text-left{justify-content:flex-start}.label-widget.text-right{justify-content:flex-end}.label-widget.has-background{color:var(--mat-sys-on-surface, #1f1f1f)}.label-widget:hover{opacity:.3;color:var(--mat-sys-primary, #6750a4)}.label-text{width:100%;text-align:center;overflow-wrap:break-word;transition:color .2s ease}.text-left .label-text{text-align:left}.text-right .label-text{text-align:right}.font-bold .label-text{font-weight:700}.label-text[responsiveText]{overflow-wrap:normal}.svg-wrapper{overflow:hidden}.svg-placeholder{width:min(80cqw,80cqh);aspect-ratio:1/1;opacity:.3;transition:transform .3s ease-in-out,opacity .3s ease,color .2s ease;transform-origin:center center;color:var(--mat-sys-on-surface-variant, #6c757d)}.has-background .svg-placeholder{color:var(--mat-sys-on-surface, #1f1f1f)}.svg-placeholder ::ng-deep svg{width:100%;height:100%;display:block}.svg-wrapper:hover .svg-placeholder{color:var(--mat-sys-primary, #6750a4)}\n"], dependencies: [{ kind: "directive", type: ResponsiveTextDirective, selector: "[libResponsiveText]", inputs: ["minFontSize", "maxFontSize", "lineHeight", "observeMutations", "debounceMs"] }] });
|
|
976
980
|
}
|
|
977
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
981
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: LabelWidgetComponent, decorators: [{
|
|
978
982
|
type: Component,
|
|
979
|
-
args: [{ selector: 'ngx-dashboard-label-widget', imports: [ResponsiveTextDirective], template: "@if (hasContent) {\r\n<div\r\n class=\"label-widget\"\r\n [style.fontSize.rem]=\"state().responsive ? null : state().fontSize! / 16\"\r\n [style.--widget-opacity]=\"state().opacity\"\r\n [class.text-left]=\"state().alignment === 'left'\"\r\n [class.text-right]=\"state().alignment === 'right'\"\r\n [class.font-bold]=\"state().fontWeight === 'bold'\"\r\n [class.has-background]=\"state().hasBackground\"\r\n>\r\n @if (state().responsive) {\r\n <div class=\"label-text\"
|
|
983
|
+
args: [{ selector: 'ngx-dashboard-label-widget', imports: [ResponsiveTextDirective], template: "@if (hasContent) {\r\n<div\r\n class=\"label-widget\"\r\n [style.fontSize.rem]=\"state().responsive ? null : state().fontSize! / 16\"\r\n [style.--widget-opacity]=\"state().opacity\"\r\n [class.text-left]=\"state().alignment === 'left'\"\r\n [class.text-right]=\"state().alignment === 'right'\"\r\n [class.font-bold]=\"state().fontWeight === 'bold'\"\r\n [class.has-background]=\"state().hasBackground\"\r\n>\r\n @if (state().responsive) {\r\n <div class=\"label-text\" libResponsiveText [minFontSize]=\"minFontSize()\" [maxFontSize]=\"maxFontSize()\">{{ label }}</div>\r\n } @else {\r\n <div class=\"label-text\">{{ label }}</div>\r\n }\r\n</div>\r\n} @else {\r\n<div class=\"svg-wrapper\" [class.has-background]=\"state().hasBackground\">\r\n <div class=\"svg-placeholder\" [innerHTML]=\"safeSvgIcon\"></div>\r\n</div>\r\n}\r\n", styles: [":host{display:block;container-type:size;width:100%;height:100%;overflow:hidden}.svg-wrapper,.label-widget{display:flex;align-items:center;justify-content:center;height:100%;width:100%;box-sizing:border-box;transition:background-color var(--mat-sys-motion-duration-medium2) var(--mat-sys-motion-easing-standard)}.has-background.svg-wrapper,.has-background.label-widget{background-color:var(--mat-sys-surface-container-high);border-radius:4px}.label-widget{overflow:hidden;container-type:size;padding:var(--mat-sys-spacing-4);color:var(--mat-sys-on-surface-variant, #6c757d);opacity:var(--widget-opacity, 1)}.label-widget.text-left{justify-content:flex-start}.label-widget.text-right{justify-content:flex-end}.label-widget.has-background{color:var(--mat-sys-on-surface, #1f1f1f)}.label-widget:hover{opacity:.3;color:var(--mat-sys-primary, #6750a4)}.label-text{width:100%;text-align:center;overflow-wrap:break-word;transition:color .2s ease}.text-left .label-text{text-align:left}.text-right .label-text{text-align:right}.font-bold .label-text{font-weight:700}.label-text[responsiveText]{overflow-wrap:normal}.svg-wrapper{overflow:hidden}.svg-placeholder{width:min(80cqw,80cqh);aspect-ratio:1/1;opacity:.3;transition:transform .3s ease-in-out,opacity .3s ease,color .2s ease;transform-origin:center center;color:var(--mat-sys-on-surface-variant, #6c757d)}.has-background .svg-placeholder{color:var(--mat-sys-on-surface, #1f1f1f)}.svg-placeholder ::ng-deep svg{width:100%;height:100%;display:block}.svg-wrapper:hover .svg-placeholder{color:var(--mat-sys-primary, #6750a4)}\n"] }]
|
|
980
984
|
}] });
|
|
981
985
|
|
|
982
|
-
const svgIcon = `
|
|
986
|
+
const svgIcon$1 = `
|
|
983
987
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" preserveAspectRatio="xMidYMid meet">
|
|
984
988
|
<use transform="matrix(-1,0,0,1,800,0)" href="#one-half" />
|
|
985
989
|
<g id="one-half">
|
|
@@ -1010,12 +1014,14 @@ const svgIcon = `
|
|
|
1010
1014
|
<path
|
|
1011
1015
|
class="clock-hour-hand"
|
|
1012
1016
|
id="anim-clock-hour-hand"
|
|
1017
|
+
fill="currentColor"
|
|
1013
1018
|
d="m 381.925,476 h 36.15 l 5e-4,-300.03008 L 400,156.25 381.9245,175.96992 Z"
|
|
1014
1019
|
transform="rotate(110.2650694444, 400, 400)"
|
|
1015
1020
|
/>
|
|
1016
1021
|
<path
|
|
1017
1022
|
class="clock-minute-hand"
|
|
1018
1023
|
id="anim-clock-minute-hand"
|
|
1024
|
+
fill="currentColor"
|
|
1019
1025
|
d="M 412.063,496.87456 H 387.937 L 385.249,65.68306 400,52.75 414.751,65.68306 Z"
|
|
1020
1026
|
transform="rotate(243.1808333333, 400, 400)"
|
|
1021
1027
|
/>
|
|
@@ -1026,10 +1032,10 @@ class ClockStateDialogComponent {
|
|
|
1026
1032
|
data = inject(MAT_DIALOG_DATA);
|
|
1027
1033
|
dialogRef = inject((MatDialogRef));
|
|
1028
1034
|
// State signals
|
|
1029
|
-
mode = signal(this.data.mode ?? 'digital');
|
|
1030
|
-
hasBackground = signal(this.data.hasBackground ?? true);
|
|
1031
|
-
timeFormat = signal(this.data.timeFormat ?? '24h');
|
|
1032
|
-
showSeconds = signal(this.data.showSeconds ?? true);
|
|
1035
|
+
mode = signal(this.data.mode ?? 'digital', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
1036
|
+
hasBackground = signal(this.data.hasBackground ?? true, ...(ngDevMode ? [{ debugName: "hasBackground" }] : []));
|
|
1037
|
+
timeFormat = signal(this.data.timeFormat ?? '24h', ...(ngDevMode ? [{ debugName: "timeFormat" }] : []));
|
|
1038
|
+
showSeconds = signal(this.data.showSeconds ?? true, ...(ngDevMode ? [{ debugName: "showSeconds" }] : []));
|
|
1033
1039
|
// Store original values for comparison
|
|
1034
1040
|
originalMode = this.data.mode ?? 'digital';
|
|
1035
1041
|
originalHasBackground = this.data.hasBackground ?? true;
|
|
@@ -1039,7 +1045,7 @@ class ClockStateDialogComponent {
|
|
|
1039
1045
|
hasChanged = computed(() => this.mode() !== this.originalMode ||
|
|
1040
1046
|
this.hasBackground() !== this.originalHasBackground ||
|
|
1041
1047
|
this.timeFormat() !== this.originalTimeFormat ||
|
|
1042
|
-
this.showSeconds() !== this.originalShowSeconds);
|
|
1048
|
+
this.showSeconds() !== this.originalShowSeconds, ...(ngDevMode ? [{ debugName: "hasChanged" }] : []));
|
|
1043
1049
|
onCancel() {
|
|
1044
1050
|
this.dialogRef.close();
|
|
1045
1051
|
}
|
|
@@ -1051,13 +1057,14 @@ class ClockStateDialogComponent {
|
|
|
1051
1057
|
showSeconds: this.showSeconds(),
|
|
1052
1058
|
});
|
|
1053
1059
|
}
|
|
1054
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.
|
|
1055
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.
|
|
1060
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ClockStateDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1061
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.1", type: ClockStateDialogComponent, isStandalone: true, selector: "lib-clock-state-dialog", ngImport: i0, template: `
|
|
1056
1062
|
<h2 mat-dialog-title>Clock Settings</h2>
|
|
1057
1063
|
<mat-dialog-content>
|
|
1058
1064
|
<div class="mode-selection">
|
|
1059
|
-
<label class="section-label">Display Mode</label>
|
|
1065
|
+
<label class="section-label" for="mode-selection-group">Display Mode</label>
|
|
1060
1066
|
<mat-radio-group
|
|
1067
|
+
id="mode-selection-group"
|
|
1061
1068
|
[value]="mode()"
|
|
1062
1069
|
(change)="mode.set($any($event.value))"
|
|
1063
1070
|
>
|
|
@@ -1069,8 +1076,9 @@ class ClockStateDialogComponent {
|
|
|
1069
1076
|
<!-- Time Format (only for digital mode) -->
|
|
1070
1077
|
@if (mode() === 'digital') {
|
|
1071
1078
|
<div class="format-selection">
|
|
1072
|
-
<label class="section-label">Time Format</label>
|
|
1079
|
+
<label class="section-label" for="time-format-group">Time Format</label>
|
|
1073
1080
|
<mat-radio-group
|
|
1081
|
+
id="time-format-group"
|
|
1074
1082
|
[value]="timeFormat()"
|
|
1075
1083
|
(change)="timeFormat.set($any($event.value))"
|
|
1076
1084
|
>
|
|
@@ -1117,9 +1125,9 @@ class ClockStateDialogComponent {
|
|
|
1117
1125
|
</mat-dialog-actions>
|
|
1118
1126
|
`, isInline: true, styles: ["mat-dialog-content{display:block;overflow-y:auto;overflow-x:hidden}.mode-selection,.format-selection{margin-top:1rem;margin-bottom:2rem}.section-label{display:block;margin-bottom:.75rem;font-weight:500}mat-radio-group{display:flex;flex-direction:column;gap:.75rem}mat-radio-button{margin:0}.toggle-section{display:flex;align-items:center;gap:.75rem;margin-bottom:.5rem}.toggle-description{margin:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i3$1.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i3$1.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }] });
|
|
1119
1127
|
}
|
|
1120
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
1128
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ClockStateDialogComponent, decorators: [{
|
|
1121
1129
|
type: Component,
|
|
1122
|
-
args: [{ selector: '
|
|
1130
|
+
args: [{ selector: 'lib-clock-state-dialog', standalone: true, imports: [
|
|
1123
1131
|
CommonModule,
|
|
1124
1132
|
FormsModule,
|
|
1125
1133
|
MatDialogModule,
|
|
@@ -1130,8 +1138,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
1130
1138
|
<h2 mat-dialog-title>Clock Settings</h2>
|
|
1131
1139
|
<mat-dialog-content>
|
|
1132
1140
|
<div class="mode-selection">
|
|
1133
|
-
<label class="section-label">Display Mode</label>
|
|
1141
|
+
<label class="section-label" for="mode-selection-group">Display Mode</label>
|
|
1134
1142
|
<mat-radio-group
|
|
1143
|
+
id="mode-selection-group"
|
|
1135
1144
|
[value]="mode()"
|
|
1136
1145
|
(change)="mode.set($any($event.value))"
|
|
1137
1146
|
>
|
|
@@ -1143,8 +1152,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
1143
1152
|
<!-- Time Format (only for digital mode) -->
|
|
1144
1153
|
@if (mode() === 'digital') {
|
|
1145
1154
|
<div class="format-selection">
|
|
1146
|
-
<label class="section-label">Time Format</label>
|
|
1155
|
+
<label class="section-label" for="time-format-group">Time Format</label>
|
|
1147
1156
|
<mat-radio-group
|
|
1157
|
+
id="time-format-group"
|
|
1148
1158
|
[value]="timeFormat()"
|
|
1149
1159
|
(change)="timeFormat.set($any($event.value))"
|
|
1150
1160
|
>
|
|
@@ -1195,17 +1205,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
1195
1205
|
class DigitalClockComponent {
|
|
1196
1206
|
#destroyRef = inject(DestroyRef);
|
|
1197
1207
|
// Inputs
|
|
1198
|
-
timeFormat = input('24h');
|
|
1199
|
-
showSeconds = input(true);
|
|
1200
|
-
hasBackground = input(false);
|
|
1208
|
+
timeFormat = input('24h', ...(ngDevMode ? [{ debugName: "timeFormat" }] : []));
|
|
1209
|
+
showSeconds = input(true, ...(ngDevMode ? [{ debugName: "showSeconds" }] : []));
|
|
1210
|
+
hasBackground = input(false, ...(ngDevMode ? [{ debugName: "hasBackground" }] : []));
|
|
1201
1211
|
// Time tracking
|
|
1202
|
-
currentTime = signal(new Date());
|
|
1212
|
+
currentTime = signal(new Date(), ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
|
|
1203
1213
|
formattedTime = computed(() => {
|
|
1204
1214
|
const time = this.currentTime();
|
|
1205
1215
|
const format = this.timeFormat();
|
|
1206
1216
|
const showSecs = this.showSeconds();
|
|
1207
1217
|
return this.#formatTime(time, format, showSecs);
|
|
1208
|
-
});
|
|
1218
|
+
}, ...(ngDevMode ? [{ debugName: "formattedTime" }] : []));
|
|
1209
1219
|
#intervalId = null;
|
|
1210
1220
|
#formatTime(time, format, showSecs) {
|
|
1211
1221
|
let hours = time.getHours();
|
|
@@ -1255,12 +1265,12 @@ class DigitalClockComponent {
|
|
|
1255
1265
|
this.#intervalId = null;
|
|
1256
1266
|
}
|
|
1257
1267
|
}
|
|
1258
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.
|
|
1259
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.
|
|
1268
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: DigitalClockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1269
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.2.1", type: DigitalClockComponent, isStandalone: true, selector: "lib-digital-clock", inputs: { timeFormat: { classPropertyName: "timeFormat", publicName: "timeFormat", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null }, hasBackground: { classPropertyName: "hasBackground", publicName: "hasBackground", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.has-background": "hasBackground()", "class.show-pm": "timeFormat() === \"12h\"", "class.show-seconds": "showSeconds()" }, classAttribute: "clock-widget digital" }, ngImport: i0, template: "<div responsiveText class=\"digital-time\">{{ formattedTime() }}</div>\r\n", styles: [":host{display:flex;align-items:center;justify-content:center;height:100%;width:100%;box-sizing:border-box;transition:background-color var(--mat-sys-motion-duration-medium2) var(--mat-sys-motion-easing-standard);padding:var(--mat-sys-spacing-4);color:var(--mat-sys-on-surface-variant, #6c757d)}:host.has-background{background-color:var(--mat-sys-surface-container-high);border-radius:4px;color:var(--mat-sys-on-surface, #1f1f1f)}:host:hover{opacity:.8;color:var(--mat-sys-primary, #6750a4)}.digital-time{font-size:clamp(8px,min(20cqw,50cqh),200px);font-family:monospace;font-weight:500;letter-spacing:.05em;transition:color .2s ease}:host.show-pm.show-seconds .digital-time{font-size:clamp(8px,min(15cqw,50cqh),200px)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1260
1270
|
}
|
|
1261
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
1271
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: DigitalClockComponent, decorators: [{
|
|
1262
1272
|
type: Component,
|
|
1263
|
-
args: [{ selector: '
|
|
1273
|
+
args: [{ selector: 'lib-digital-clock', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
1264
1274
|
'[class.has-background]': 'hasBackground()',
|
|
1265
1275
|
'[class.show-pm]': 'timeFormat() === "12h"',
|
|
1266
1276
|
'[class.show-seconds]': 'showSeconds()',
|
|
@@ -1272,32 +1282,32 @@ class AnalogClockComponent {
|
|
|
1272
1282
|
#destroyRef = inject(DestroyRef);
|
|
1273
1283
|
#renderer = inject(Renderer2);
|
|
1274
1284
|
// Inputs
|
|
1275
|
-
hasBackground = input(false);
|
|
1276
|
-
showSeconds = input(true);
|
|
1285
|
+
hasBackground = input(false, ...(ngDevMode ? [{ debugName: "hasBackground" }] : []));
|
|
1286
|
+
showSeconds = input(true, ...(ngDevMode ? [{ debugName: "showSeconds" }] : []));
|
|
1277
1287
|
// ViewChild references for clock hands
|
|
1278
|
-
hourHand = viewChild('hourHand');
|
|
1279
|
-
minuteHand = viewChild('minuteHand');
|
|
1280
|
-
secondHand = viewChild('secondHand');
|
|
1288
|
+
hourHand = viewChild('hourHand', ...(ngDevMode ? [{ debugName: "hourHand" }] : []));
|
|
1289
|
+
minuteHand = viewChild('minuteHand', ...(ngDevMode ? [{ debugName: "minuteHand" }] : []));
|
|
1290
|
+
secondHand = viewChild('secondHand', ...(ngDevMode ? [{ debugName: "secondHand" }] : []));
|
|
1281
1291
|
// Time tracking
|
|
1282
|
-
currentTime = signal(new Date());
|
|
1292
|
+
currentTime = signal(new Date(), ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
|
|
1283
1293
|
// Computed rotation signals
|
|
1284
1294
|
secondHandRotation = computed(() => {
|
|
1285
1295
|
const seconds = this.currentTime().getSeconds();
|
|
1286
1296
|
return seconds * 6; // 360° / 60s = 6° per second
|
|
1287
|
-
});
|
|
1297
|
+
}, ...(ngDevMode ? [{ debugName: "secondHandRotation" }] : []));
|
|
1288
1298
|
minuteHandRotation = computed(() => {
|
|
1289
1299
|
const time = this.currentTime();
|
|
1290
1300
|
const minutes = time.getMinutes();
|
|
1291
1301
|
const seconds = time.getSeconds();
|
|
1292
1302
|
return minutes * 6 + seconds / 10; // Smooth minute hand movement
|
|
1293
|
-
});
|
|
1303
|
+
}, ...(ngDevMode ? [{ debugName: "minuteHandRotation" }] : []));
|
|
1294
1304
|
hourHandRotation = computed(() => {
|
|
1295
1305
|
const time = this.currentTime();
|
|
1296
1306
|
const hours = time.getHours() % 12;
|
|
1297
1307
|
const minutes = time.getMinutes();
|
|
1298
1308
|
const seconds = time.getSeconds();
|
|
1299
1309
|
return hours * 30 + minutes / 2 + seconds / 120; // Smooth hour hand movement
|
|
1300
|
-
});
|
|
1310
|
+
}, ...(ngDevMode ? [{ debugName: "hourHandRotation" }] : []));
|
|
1301
1311
|
#intervalId = null;
|
|
1302
1312
|
constructor() {
|
|
1303
1313
|
// Set up time update timer
|
|
@@ -1343,12 +1353,12 @@ class AnalogClockComponent {
|
|
|
1343
1353
|
this.#renderer.setAttribute(secondElement, 'transform', `rotate(${this.secondHandRotation()}, 400, 400)`);
|
|
1344
1354
|
}
|
|
1345
1355
|
}
|
|
1346
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.
|
|
1347
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.
|
|
1356
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: AnalogClockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1357
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.2.1", type: AnalogClockComponent, isStandalone: true, selector: "lib-analog-clock", inputs: { hasBackground: { classPropertyName: "hasBackground", publicName: "hasBackground", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.has-background": "hasBackground()", "class.show-seconds": "showSeconds()" }, classAttribute: "clock-widget analog" }, viewQueries: [{ propertyName: "hourHand", first: true, predicate: ["hourHand"], descendants: true, isSignal: true }, { propertyName: "minuteHand", first: true, predicate: ["minuteHand"], descendants: true, isSignal: true }, { propertyName: "secondHand", first: true, predicate: ["secondHand"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"analog-clock-container\">\r\n <div class=\"aspect-ratio-box\">\r\n <svg\r\n version=\"1.1\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n viewBox=\"0 0 800 800\"\r\n preserveAspectRatio=\"xMidYMid meet\"\r\n >\r\n <!-- Optional face circle; uncomment if you want a visible outline by default -->\r\n <!-- <circle cx=\"400\" cy=\"400\" r=\"400\" fill=\"transparent\" stroke=\"currentColor\" stroke-width=\"2\" /> -->\r\n\r\n <use transform=\"matrix(-1,0,0,1,800,0)\" href=\"#one-half\" />\r\n <g id=\"one-half\">\r\n <g id=\"one-fourth\">\r\n <!-- 12 / 3 / 6 / 9 heavy marks -->\r\n <path d=\"m400 40v107\" stroke-width=\"26.7\" stroke=\"currentColor\" />\r\n <g id=\"one-twelfth\">\r\n <!-- 30\u00B0 heavy marks -->\r\n <path\r\n d=\"m580 88.233-42.5 73.612\"\r\n stroke-width=\"26.7\"\r\n stroke=\"currentColor\"\r\n />\r\n <g id=\"one-thirtieth\">\r\n <!-- minute/second ticks -->\r\n <path\r\n id=\"one-sixtieth\"\r\n d=\"m437.63 41.974-3.6585 34.808\"\r\n stroke-width=\"13.6\"\r\n stroke=\"currentColor\"\r\n />\r\n <use transform=\"rotate(6 400 400)\" href=\"#one-sixtieth\" />\r\n </g>\r\n <use transform=\"rotate(12 400 400)\" href=\"#one-thirtieth\" />\r\n </g>\r\n <use transform=\"rotate(30 400 400)\" href=\"#one-twelfth\" />\r\n <use transform=\"rotate(60 400 400)\" href=\"#one-twelfth\" />\r\n </g>\r\n <use transform=\"rotate(90 400 400)\" href=\"#one-fourth\" />\r\n </g>\r\n\r\n <!-- Hands -->\r\n <path\r\n class=\"clock-hour-hand\"\r\n id=\"anim-clock-hour-hand\"\r\n #hourHand\r\n d=\"m 381.925,476 h 36.15 l 5e-4,-300.03008 L 400,156.25 381.9245,175.96992 Z\"\r\n transform=\"rotate(110.2650694444, 400, 400)\"\r\n />\r\n <path\r\n class=\"clock-minute-hand\"\r\n id=\"anim-clock-minute-hand\"\r\n #minuteHand\r\n d=\"M 412.063,496.87456 H 387.937 L 385.249,65.68306 400,52.75 414.751,65.68306 Z\"\r\n transform=\"rotate(243.1808333333, 400, 400)\"\r\n />\r\n <path\r\n class=\"clock-second-hand\"\r\n id=\"anim-clock-second-hand\"\r\n #secondHand\r\n d=\"M 397.317,63.51744 395.91962,168.4 C 374.575,170.5125 358.2,188.365 358.2,210 c 0,21.635 16.3,39 36.61214,41.47594 L 391.52847,498 h 16.94306 L 405.1868,251.47593 C 425.5,249 441.8,231.635 441.8,210 c 2e-5,-21.635 -16.375,-39.4875 -37.71971,-41.6 L 402.683,63.51744 400,60 Z M 400,190.534 c 10.888,0 19.466,8.866 19.466,19.466 0,10.6 -8.578,19.466 -19.466,19.466 -10.888,0 -19.466,-8.866 -19.466,-19.466 0,-10.6 8.578,-19.466 19.466,-19.466 z\"\r\n transform=\"rotate(190.85, 400, 400)\"\r\n />\r\n </svg>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%}.analog-clock-container{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.analog-clock-container .aspect-ratio-box{position:relative;width:100%;max-width:100%;max-height:100%;aspect-ratio:1/1}@supports not (aspect-ratio: 1/1){.analog-clock-container .aspect-ratio-box:before{content:\"\";display:block;padding-bottom:100%}.analog-clock-container .aspect-ratio-box svg{position:absolute;top:0;left:0;width:100%;height:100%}}.analog-clock-container .aspect-ratio-box svg{display:block;width:100%;height:100%}.analog-clock-container .aspect-ratio-box svg path:not(.clock-hour-hand):not(.clock-minute-hand):not(.clock-second-hand){stroke:var(--mat-sys-on-surface, #1d1b20)}.analog-clock-container .aspect-ratio-box svg .clock-hour-hand{fill:var(--mat-sys-on-surface, #1d1b20)}.analog-clock-container .aspect-ratio-box svg .clock-minute-hand{fill:var(--mat-sys-on-surface, #1d1b20)}.analog-clock-container .aspect-ratio-box svg .clock-second-hand{fill:var(--mat-sys-primary, #6750a4)}:host:not(.show-seconds) .clock-second-hand{display:none}:host.has-background svg circle{fill:var(--mat-sys-surface, #fffbfe)}:host:hover{opacity:.8}:host:hover svg .clock-hour-hand,:host:hover svg .clock-minute-hand{fill:var(--mat-sys-primary, #6750a4)}:host.clock-widget.analog{container-type:size;container-name:analog-clock}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1348
1358
|
}
|
|
1349
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
1359
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: AnalogClockComponent, decorators: [{
|
|
1350
1360
|
type: Component,
|
|
1351
|
-
args: [{ selector: '
|
|
1361
|
+
args: [{ selector: 'lib-analog-clock', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
1352
1362
|
'[class.has-background]': 'hasBackground()',
|
|
1353
1363
|
'[class.show-seconds]': 'showSeconds()',
|
|
1354
1364
|
'class': 'clock-widget analog'
|
|
@@ -1361,17 +1371,17 @@ class ClockWidgetComponent {
|
|
|
1361
1371
|
widgetTypeid: '@default/clock-widget',
|
|
1362
1372
|
name: 'Clock',
|
|
1363
1373
|
description: 'Display time in analog or digital format',
|
|
1364
|
-
svgIcon,
|
|
1374
|
+
svgIcon: svgIcon$1,
|
|
1365
1375
|
};
|
|
1366
1376
|
#sanitizer = inject(DomSanitizer);
|
|
1367
1377
|
#dialog = inject(MatDialog);
|
|
1368
|
-
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon);
|
|
1378
|
+
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon$1);
|
|
1369
1379
|
state = signal({
|
|
1370
1380
|
mode: 'analog',
|
|
1371
1381
|
hasBackground: true,
|
|
1372
1382
|
timeFormat: '24h',
|
|
1373
1383
|
showSeconds: true,
|
|
1374
|
-
});
|
|
1384
|
+
}, ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
1375
1385
|
constructor() {
|
|
1376
1386
|
// No timer logic needed - DigitalClock manages its own time
|
|
1377
1387
|
}
|
|
@@ -1408,14 +1418,827 @@ class ClockWidgetComponent {
|
|
|
1408
1418
|
get isDigital() {
|
|
1409
1419
|
return this.state().mode === 'digital';
|
|
1410
1420
|
}
|
|
1411
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.
|
|
1412
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.
|
|
1421
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ClockWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1422
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.1", type: ClockWidgetComponent, isStandalone: true, selector: "ngx-dashboard-clock-widget", ngImport: i0, template: "@if (isDigital) {\r\n <lib-digital-clock\r\n [timeFormat]=\"state().timeFormat || '24h'\"\r\n [showSeconds]=\"state().showSeconds ?? true\"\r\n [hasBackground]=\"state().hasBackground ?? false\"\r\n />\r\n} @else if (isAnalog) {\r\n <lib-analog-clock\r\n [hasBackground]=\"state().hasBackground ?? false\"\r\n [showSeconds]=\"state().showSeconds ?? true\"\r\n />\r\n} @else {\r\n<div class=\"svg-wrapper\" [class.has-background]=\"state().hasBackground\">\r\n <div class=\"svg-placeholder\" [innerHTML]=\"safeSvgIcon\"></div>\r\n</div>\r\n}", styles: [":host{display:block;container-type:size;width:100%;height:100%;overflow:hidden}.svg-wrapper,.clock-widget{display:flex;align-items:center;justify-content:center;height:100%;width:100%;box-sizing:border-box;transition:background-color var(--mat-sys-motion-duration-medium2) var(--mat-sys-motion-easing-standard)}.has-background.svg-wrapper,.has-background.clock-widget{background-color:var(--mat-sys-surface-container-high);border-radius:4px}.clock-widget{padding:var(--mat-sys-spacing-4);color:var(--mat-sys-on-surface-variant, #6c757d)}.clock-widget.has-background{color:var(--mat-sys-on-surface, #1f1f1f)}.clock-widget:hover{opacity:.8;color:var(--mat-sys-primary, #6750a4)}.analog-clock{width:min(80cqw,80cqh);aspect-ratio:1/1;position:relative}.clock-face{width:100%;height:100%;border:2px solid currentColor;border-radius:50%;position:relative}.clock-face:before,.clock-face:after{content:\"\";position:absolute;background-color:currentColor;left:50%;transform:translate(-50%)}.clock-face:before{width:2px;height:10%;top:0}.clock-face:after{width:2px;height:10%;bottom:0}.hour-hand,.minute-hand{position:absolute;background-color:currentColor;left:50%;bottom:50%;transform-origin:50% 100%;border-radius:2px}.hour-hand{width:4px;height:25%;transform:translate(-50%) rotate(30deg)}.minute-hand{width:2px;height:35%;transform:translate(-50%) rotate(90deg)}.center-dot{position:absolute;width:8px;height:8px;background-color:currentColor;border-radius:50%;top:50%;left:50%;transform:translate(-50%,-50%)}.svg-wrapper{overflow:hidden}.svg-placeholder{width:min(80cqw,80cqh);aspect-ratio:1/1;opacity:.3;transition:transform .3s ease-in-out,opacity .3s ease;transform-origin:center center}.svg-placeholder ::ng-deep svg{width:100%;height:100%;display:block}.svg-placeholder ::ng-deep svg .clock-face{stroke:var(--mat-sys-on-surface, #1d1b20)}.svg-placeholder ::ng-deep svg .clock-hour-hand{fill:var(--mat-sys-on-surface, #1d1b20)}.svg-placeholder ::ng-deep svg .clock-minute-hand{fill:var(--mat-sys-on-surface, #1d1b20)}.svg-placeholder ::ng-deep svg .clock-second-hand{fill:var(--mat-sys-primary, #6750a4)}.has-background .svg-placeholder ::ng-deep svg circle{fill:var(--mat-sys-surface, #fffbfe)}\n"], dependencies: [{ kind: "component", type: DigitalClockComponent, selector: "lib-digital-clock", inputs: ["timeFormat", "showSeconds", "hasBackground"] }, { kind: "component", type: AnalogClockComponent, selector: "lib-analog-clock", inputs: ["hasBackground", "showSeconds"] }] });
|
|
1423
|
+
}
|
|
1424
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ClockWidgetComponent, decorators: [{
|
|
1425
|
+
type: Component,
|
|
1426
|
+
args: [{ selector: 'ngx-dashboard-clock-widget', standalone: true, imports: [DigitalClockComponent, AnalogClockComponent], template: "@if (isDigital) {\r\n <lib-digital-clock\r\n [timeFormat]=\"state().timeFormat || '24h'\"\r\n [showSeconds]=\"state().showSeconds ?? true\"\r\n [hasBackground]=\"state().hasBackground ?? false\"\r\n />\r\n} @else if (isAnalog) {\r\n <lib-analog-clock\r\n [hasBackground]=\"state().hasBackground ?? false\"\r\n [showSeconds]=\"state().showSeconds ?? true\"\r\n />\r\n} @else {\r\n<div class=\"svg-wrapper\" [class.has-background]=\"state().hasBackground\">\r\n <div class=\"svg-placeholder\" [innerHTML]=\"safeSvgIcon\"></div>\r\n</div>\r\n}", styles: [":host{display:block;container-type:size;width:100%;height:100%;overflow:hidden}.svg-wrapper,.clock-widget{display:flex;align-items:center;justify-content:center;height:100%;width:100%;box-sizing:border-box;transition:background-color var(--mat-sys-motion-duration-medium2) var(--mat-sys-motion-easing-standard)}.has-background.svg-wrapper,.has-background.clock-widget{background-color:var(--mat-sys-surface-container-high);border-radius:4px}.clock-widget{padding:var(--mat-sys-spacing-4);color:var(--mat-sys-on-surface-variant, #6c757d)}.clock-widget.has-background{color:var(--mat-sys-on-surface, #1f1f1f)}.clock-widget:hover{opacity:.8;color:var(--mat-sys-primary, #6750a4)}.analog-clock{width:min(80cqw,80cqh);aspect-ratio:1/1;position:relative}.clock-face{width:100%;height:100%;border:2px solid currentColor;border-radius:50%;position:relative}.clock-face:before,.clock-face:after{content:\"\";position:absolute;background-color:currentColor;left:50%;transform:translate(-50%)}.clock-face:before{width:2px;height:10%;top:0}.clock-face:after{width:2px;height:10%;bottom:0}.hour-hand,.minute-hand{position:absolute;background-color:currentColor;left:50%;bottom:50%;transform-origin:50% 100%;border-radius:2px}.hour-hand{width:4px;height:25%;transform:translate(-50%) rotate(30deg)}.minute-hand{width:2px;height:35%;transform:translate(-50%) rotate(90deg)}.center-dot{position:absolute;width:8px;height:8px;background-color:currentColor;border-radius:50%;top:50%;left:50%;transform:translate(-50%,-50%)}.svg-wrapper{overflow:hidden}.svg-placeholder{width:min(80cqw,80cqh);aspect-ratio:1/1;opacity:.3;transition:transform .3s ease-in-out,opacity .3s ease;transform-origin:center center}.svg-placeholder ::ng-deep svg{width:100%;height:100%;display:block}.svg-placeholder ::ng-deep svg .clock-face{stroke:var(--mat-sys-on-surface, #1d1b20)}.svg-placeholder ::ng-deep svg .clock-hour-hand{fill:var(--mat-sys-on-surface, #1d1b20)}.svg-placeholder ::ng-deep svg .clock-minute-hand{fill:var(--mat-sys-on-surface, #1d1b20)}.svg-placeholder ::ng-deep svg .clock-second-hand{fill:var(--mat-sys-primary, #6750a4)}.has-background .svg-placeholder ::ng-deep svg circle{fill:var(--mat-sys-surface, #fffbfe)}\n"] }]
|
|
1427
|
+
}], ctorParameters: () => [] });
|
|
1428
|
+
|
|
1429
|
+
/**
|
|
1430
|
+
* Responsive radial gauge component with hybrid sizing and thickness control.
|
|
1431
|
+
*
|
|
1432
|
+
* This component provides a highly flexible gauge system with three independent
|
|
1433
|
+
* control dimensions that can be mixed and matched for different use cases:
|
|
1434
|
+
*
|
|
1435
|
+
* ## Size Control:
|
|
1436
|
+
* - **Fixed Size**: Use manual `size` input (traditional behavior)
|
|
1437
|
+
* - **Container Responsive**: Enable `fitToContainer` for automatic sizing
|
|
1438
|
+
*
|
|
1439
|
+
* ## Thickness Control:
|
|
1440
|
+
* - **Manual Thickness**: Use individual thickness inputs (traditional behavior)
|
|
1441
|
+
* - **Proportional Thickness**: Enable `responsiveMode` for size-based scaling
|
|
1442
|
+
*
|
|
1443
|
+
* ## Usage Scenarios:
|
|
1444
|
+
*
|
|
1445
|
+
* ### 1. Dashboard Widgets (Recommended)
|
|
1446
|
+
* ```html
|
|
1447
|
+
* <ngx-radial-gauge
|
|
1448
|
+
* [value]="cpuUsage"
|
|
1449
|
+
* [fitToContainer]="true"
|
|
1450
|
+
* [responsiveMode]="true"
|
|
1451
|
+
* [sizeToThicknessRatio]="12" />
|
|
1452
|
+
* ```
|
|
1453
|
+
* **Best for**: Grid layouts, dashboard panels, adaptive containers
|
|
1454
|
+
* **Behavior**: Automatically resizes to fit available space while maintaining
|
|
1455
|
+
* consistent proportional appearance across all sizes.
|
|
1456
|
+
*
|
|
1457
|
+
* ### 2. Fixed Layouts (Traditional)
|
|
1458
|
+
* ```html
|
|
1459
|
+
* <ngx-radial-gauge
|
|
1460
|
+
* [value]="temperature"
|
|
1461
|
+
* [size]="300"
|
|
1462
|
+
* [outerThickness]="36"
|
|
1463
|
+
* [innerThickness]="12" />
|
|
1464
|
+
* ```
|
|
1465
|
+
* **Best for**: Static designs, precise sizing requirements, print layouts
|
|
1466
|
+
* **Behavior**: Exact pixel control over all dimensions, predictable appearance.
|
|
1467
|
+
*
|
|
1468
|
+
* ### 3. Scalable Designs
|
|
1469
|
+
* ```html
|
|
1470
|
+
* <ngx-radial-gauge
|
|
1471
|
+
* [value]="batteryLevel"
|
|
1472
|
+
* [size]="gaugeSize"
|
|
1473
|
+
* [responsiveMode]="true"
|
|
1474
|
+
* [sizeToThicknessRatio]="20" />
|
|
1475
|
+
* ```
|
|
1476
|
+
* **Best for**: User-configurable sizing, responsive breakpoints, zoom interfaces
|
|
1477
|
+
* **Behavior**: Manual size control with automatic thickness scaling. As size
|
|
1478
|
+
* increases/decreases, ring thickness scales proportionally to maintain visual balance.
|
|
1479
|
+
*
|
|
1480
|
+
* ## Mathematical Relationships:
|
|
1481
|
+
*
|
|
1482
|
+
* When `responsiveMode=true`, thickness follows this formula:
|
|
1483
|
+
* ```
|
|
1484
|
+
* baseThickness = effectiveSize / sizeToThicknessRatio
|
|
1485
|
+
* outerThickness = baseThickness × responsiveProportions.outer (default: 3)
|
|
1486
|
+
* innerThickness = baseThickness × responsiveProportions.inner (default: 1)
|
|
1487
|
+
* gap = baseThickness × responsiveProportions.gap (default: 0.5)
|
|
1488
|
+
* totalThickness = baseThickness × 4.5 (outer + inner + gap)
|
|
1489
|
+
* ```
|
|
1490
|
+
*
|
|
1491
|
+
* Example with 300px gauge and ratio=20 (ultra-thin):
|
|
1492
|
+
* - baseThickness = 15px
|
|
1493
|
+
* - outerThickness = 45px (15×3)
|
|
1494
|
+
* - innerThickness = 15px (15×1)
|
|
1495
|
+
* - gap = 7.5px (15×0.5)
|
|
1496
|
+
* - totalThickness = 67.5px (22.5% of diameter)
|
|
1497
|
+
*
|
|
1498
|
+
* ## Container Responsiveness:
|
|
1499
|
+
*
|
|
1500
|
+
* When `fitToContainer=true`, the component uses ResizeObserver to:
|
|
1501
|
+
* 1. Monitor parent container dimension changes
|
|
1502
|
+
* 2. Calculate maximum diameter maintaining 2:1 aspect ratio (width:height)
|
|
1503
|
+
* 3. Apply containerPadding for safe margins
|
|
1504
|
+
* 4. Update gauge size in real-time
|
|
1505
|
+
*
|
|
1506
|
+
* This provides true responsive behavior for dashboard widgets, grid layouts,
|
|
1507
|
+
* and adaptive interfaces.
|
|
1508
|
+
*
|
|
1509
|
+
* ## Accessibility:
|
|
1510
|
+
*
|
|
1511
|
+
* The component implements ARIA meter role with proper labeling:
|
|
1512
|
+
* - `role="meter"` for semantic meaning
|
|
1513
|
+
* - `aria-valuemin/max/now` for screen readers
|
|
1514
|
+
* - `aria-label` with contextual information
|
|
1515
|
+
* - Internationalized number formatting
|
|
1516
|
+
*
|
|
1517
|
+
*/
|
|
1518
|
+
class RadialGaugeComponent {
|
|
1519
|
+
valueTextEl = viewChild('valueText', ...(ngDevMode ? [{ debugName: "valueTextEl" }] : []));
|
|
1520
|
+
valueGroupEl = viewChild('valueGroup', ...(ngDevMode ? [{ debugName: "valueGroupEl" }] : []));
|
|
1521
|
+
refTextEl = viewChild.required('refText');
|
|
1522
|
+
// Core Inputs - Value and Range
|
|
1523
|
+
value = input(0, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
1524
|
+
min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : []));
|
|
1525
|
+
max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : []));
|
|
1526
|
+
segments = input(...(ngDevMode ? [undefined, { debugName: "segments" }] : []));
|
|
1527
|
+
title = input('Gauge', ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
1528
|
+
description = input('', ...(ngDevMode ? [{ debugName: "description" }] : []));
|
|
1529
|
+
segmentGapPx = input(4, ...(ngDevMode ? [{ debugName: "segmentGapPx" }] : []));
|
|
1530
|
+
// Widget styling inputs
|
|
1531
|
+
/**
|
|
1532
|
+
* Whether the gauge should display with a background.
|
|
1533
|
+
* Affects text color contrast and other visual elements.
|
|
1534
|
+
* @default false
|
|
1535
|
+
*/
|
|
1536
|
+
hasBackground = input(false, ...(ngDevMode ? [{ debugName: "hasBackground" }] : []));
|
|
1537
|
+
/**
|
|
1538
|
+
* Whether to display the numeric value label in the center of the gauge.
|
|
1539
|
+
* @default true
|
|
1540
|
+
*/
|
|
1541
|
+
showValueLabel = input(true, ...(ngDevMode ? [{ debugName: "showValueLabel" }] : []));
|
|
1542
|
+
// Size Control Inputs
|
|
1543
|
+
/**
|
|
1544
|
+
* Base gauge diameter in pixels. Used as fallback when fitToContainer is false.
|
|
1545
|
+
* @default 300
|
|
1546
|
+
*/
|
|
1547
|
+
size = input(300, ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
1548
|
+
/**
|
|
1549
|
+
* Automatically resize gauge to fit its container dimensions.
|
|
1550
|
+
* When true, the gauge will observe container size changes and adjust accordingly.
|
|
1551
|
+
* Maintains semicircle aspect ratio (2:1 width:height).
|
|
1552
|
+
* @default false
|
|
1553
|
+
*/
|
|
1554
|
+
fitToContainer = input(false, ...(ngDevMode ? [{ debugName: "fitToContainer" }] : []));
|
|
1555
|
+
/**
|
|
1556
|
+
* Padding in pixels to maintain from container edges when fitToContainer is true.
|
|
1557
|
+
* @default 10
|
|
1558
|
+
*/
|
|
1559
|
+
containerPadding = input(10, ...(ngDevMode ? [{ debugName: "containerPadding" }] : []));
|
|
1560
|
+
// Thickness Control Inputs
|
|
1561
|
+
/**
|
|
1562
|
+
* Use proportional thickness scaling based on gauge size.
|
|
1563
|
+
* When true, all thickness values are calculated as multiples of baseThickness.
|
|
1564
|
+
* Overrides manual outerThickness, innerThickness, and gap inputs.
|
|
1565
|
+
* @default false
|
|
1566
|
+
*/
|
|
1567
|
+
responsiveMode = input(false, ...(ngDevMode ? [{ debugName: "responsiveMode" }] : []));
|
|
1568
|
+
/**
|
|
1569
|
+
* Ratio used to calculate base thickness from gauge size.
|
|
1570
|
+
* baseThickness = effectiveSize / sizeToThicknessRatio
|
|
1571
|
+
* Higher values create thinner gauge rings for ultra-thin appearance.
|
|
1572
|
+
* @default 20
|
|
1573
|
+
* @example
|
|
1574
|
+
* - ratio=15: thicker rings (bt = size/15)
|
|
1575
|
+
* - ratio=20: ultra-thin balanced appearance (bt = size/20)
|
|
1576
|
+
* - ratio=30: extremely thin rings (bt = size/30)
|
|
1577
|
+
*/
|
|
1578
|
+
sizeToThicknessRatio = input(20, ...(ngDevMode ? [{ debugName: "sizeToThicknessRatio" }] : []));
|
|
1579
|
+
/**
|
|
1580
|
+
* Proportional multipliers for responsive thickness calculations.
|
|
1581
|
+
* - outer: Multiplier for outer ring thickness (default: 3)
|
|
1582
|
+
* - inner: Multiplier for inner ring thickness (default: 1)
|
|
1583
|
+
* - gap: Multiplier for gap between rings (default: 0.5)
|
|
1584
|
+
* Total thickness = baseThickness × (outer + inner + gap) = bt × 4.5
|
|
1585
|
+
* @default { outer: 3, inner: 1, gap: 0.5 }
|
|
1586
|
+
*/
|
|
1587
|
+
responsiveProportions = input({ outer: 3, inner: 1, gap: 0.5 }, ...(ngDevMode ? [{ debugName: "responsiveProportions" }] : []));
|
|
1588
|
+
// Manual Thickness Inputs (used when responsiveMode is false)
|
|
1589
|
+
/**
|
|
1590
|
+
* Manual outer ring thickness in pixels. Ignored when responsiveMode is true.
|
|
1591
|
+
* @default 36
|
|
1592
|
+
*/
|
|
1593
|
+
outerThickness = input(36, ...(ngDevMode ? [{ debugName: "outerThickness" }] : []));
|
|
1594
|
+
/**
|
|
1595
|
+
* Manual inner ring thickness in pixels. Ignored when responsiveMode is true.
|
|
1596
|
+
* @default 12
|
|
1597
|
+
*/
|
|
1598
|
+
innerThickness = input(12, ...(ngDevMode ? [{ debugName: "innerThickness" }] : []));
|
|
1599
|
+
/**
|
|
1600
|
+
* Manual gap between rings in pixels. Ignored when responsiveMode is true.
|
|
1601
|
+
* @default 8
|
|
1602
|
+
*/
|
|
1603
|
+
gap = input(8, ...(ngDevMode ? [{ debugName: "gap" }] : []));
|
|
1604
|
+
titleId = `rg-title-${Math.random().toString(36).slice(2)}`;
|
|
1605
|
+
descId = `rg-desc-${Math.random().toString(36).slice(2)}`;
|
|
1606
|
+
clipId = `rg-clip-${Math.random().toString(36).slice(2)}`;
|
|
1607
|
+
locale = inject(LOCALE_ID);
|
|
1608
|
+
elementRef = inject((ElementRef));
|
|
1609
|
+
destroyRef = inject(DestroyRef);
|
|
1610
|
+
nf = new Intl.NumberFormat(this.locale, {
|
|
1611
|
+
maximumFractionDigits: 1,
|
|
1612
|
+
});
|
|
1613
|
+
// Container Size Detection
|
|
1614
|
+
/**
|
|
1615
|
+
* Tracks the container's available size for responsive sizing.
|
|
1616
|
+
* Updated by ResizeObserver when fitToContainer is enabled.
|
|
1617
|
+
* @private
|
|
1618
|
+
*/
|
|
1619
|
+
containerSize = signal(null, ...(ngDevMode ? [{ debugName: "containerSize" }] : []));
|
|
1620
|
+
/**
|
|
1621
|
+
* ResizeObserver instance for monitoring container size changes.
|
|
1622
|
+
* Created when fitToContainer is enabled, destroyed on component cleanup.
|
|
1623
|
+
* @private
|
|
1624
|
+
*/
|
|
1625
|
+
resizeObserver = null;
|
|
1626
|
+
viewReady = toSignal(from(new Promise((resolve) => afterNextRender(resolve))).pipe(map(() => true)), { initialValue: false });
|
|
1627
|
+
fontsReady = toSignal(typeof document !== 'undefined' && 'fonts' in document
|
|
1628
|
+
? from(document.fonts.ready).pipe(map(() => true))
|
|
1629
|
+
: of(true), // SSR or older browsers: treat as ready
|
|
1630
|
+
{ initialValue: false });
|
|
1631
|
+
constructor() {
|
|
1632
|
+
this.destroyRef.onDestroy(() => {
|
|
1633
|
+
if (this.resizeObserver) {
|
|
1634
|
+
this.resizeObserver.disconnect();
|
|
1635
|
+
this.resizeObserver = null;
|
|
1636
|
+
}
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Effect that manages ResizeObserver lifecycle based on fitToContainer input.
|
|
1641
|
+
* Automatically connects/disconnects observer when the input changes.
|
|
1642
|
+
* @private
|
|
1643
|
+
*/
|
|
1644
|
+
containerObserverEffect = effect(() => {
|
|
1645
|
+
const shouldObserve = this.fitToContainer();
|
|
1646
|
+
if (shouldObserve && !this.resizeObserver) {
|
|
1647
|
+
// Create and start observing
|
|
1648
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
1649
|
+
const entry = entries[0];
|
|
1650
|
+
if (!entry)
|
|
1651
|
+
return;
|
|
1652
|
+
const { width, height } = entry.contentRect;
|
|
1653
|
+
const padding = this.containerPadding();
|
|
1654
|
+
const availW = Math.max(0, width - padding * 2);
|
|
1655
|
+
const availH = Math.max(0, height - padding);
|
|
1656
|
+
let sFromW;
|
|
1657
|
+
let sFromH;
|
|
1658
|
+
if (this.responsiveMode()) {
|
|
1659
|
+
// In responsive mode: outerThickness = 3 * baseThickness = 3 * size / ratio
|
|
1660
|
+
// Total space needed = size + outerThickness = size + 3*size/ratio = size * (1 + 3/ratio)
|
|
1661
|
+
const ratio = this.sizeToThicknessRatio();
|
|
1662
|
+
const spaceFactor = 1 + 3 / ratio; // Total space factor
|
|
1663
|
+
sFromW = availW / spaceFactor;
|
|
1664
|
+
sFromH = (2 * availH) / spaceFactor;
|
|
1665
|
+
}
|
|
1666
|
+
else {
|
|
1667
|
+
// Manual thickness: outer thickness is fixed
|
|
1668
|
+
const outerT = this.outerThickness();
|
|
1669
|
+
sFromW = Math.max(0, availW - outerT);
|
|
1670
|
+
sFromH = Math.max(0, 2 * availH - outerT);
|
|
1671
|
+
}
|
|
1672
|
+
const maxDiameter = Math.min(sFromW, sFromH);
|
|
1673
|
+
this.containerSize.set(Math.max(maxDiameter, 50));
|
|
1674
|
+
});
|
|
1675
|
+
this.resizeObserver.observe(this.elementRef.nativeElement);
|
|
1676
|
+
}
|
|
1677
|
+
else if (!shouldObserve && this.resizeObserver) {
|
|
1678
|
+
// Stop observing and cleanup
|
|
1679
|
+
this.resizeObserver.disconnect();
|
|
1680
|
+
this.resizeObserver = null;
|
|
1681
|
+
this.containerSize.set(null);
|
|
1682
|
+
}
|
|
1683
|
+
}, ...(ngDevMode ? [{ debugName: "containerObserverEffect" }] : []));
|
|
1684
|
+
// ── Build the reference string reactively ───────────────────────────────────
|
|
1685
|
+
referenceString = computed(() => {
|
|
1686
|
+
const ref = this.labelReference();
|
|
1687
|
+
if (typeof ref === 'string')
|
|
1688
|
+
return ref;
|
|
1689
|
+
if (typeof ref === 'number' && ref > 0) {
|
|
1690
|
+
const g = this.referenceGlyph() ?? '0';
|
|
1691
|
+
return g.repeat(ref);
|
|
1692
|
+
}
|
|
1693
|
+
return this.formattedLabel(); // measure actual label
|
|
1694
|
+
}, ...(ngDevMode ? [{ debugName: "referenceString" }] : []));
|
|
1695
|
+
// ── Core transform: center + uniform scale to fit the reserved box ──────────
|
|
1696
|
+
valueTransform = computed(() => {
|
|
1697
|
+
if (!this.showValueLabel())
|
|
1698
|
+
return '';
|
|
1699
|
+
// ensure we wait for first paint + font shaping
|
|
1700
|
+
this.viewReady();
|
|
1701
|
+
this.fontsReady();
|
|
1702
|
+
const cx = this.centerX();
|
|
1703
|
+
const cy = this.centerY();
|
|
1704
|
+
const r = this.legendInnerRadius();
|
|
1705
|
+
const pad = this.labelPadding();
|
|
1706
|
+
const boxWidth = Math.max(0, 2 * r - 2 * pad);
|
|
1707
|
+
const boxHeight = Math.max(0, r - pad);
|
|
1708
|
+
// If geometry is degenerate, just center.
|
|
1709
|
+
if (!boxWidth || !boxHeight)
|
|
1710
|
+
return `translate(${cx},${cy})`;
|
|
1711
|
+
// Measure the actual label (for height) and the reference (for width)
|
|
1712
|
+
const labelEl = this.valueTextEl()?.nativeElement;
|
|
1713
|
+
const refEl = this.refTextEl().nativeElement;
|
|
1714
|
+
if (!labelEl)
|
|
1715
|
+
return `translate(${cx},${cy})`;
|
|
1716
|
+
// Important: ensure text nodes are up to date before reading BBox
|
|
1717
|
+
// (Angular's computed/effect guarantees sync within the same microtask)
|
|
1718
|
+
const labelBox = this.safeBBox(labelEl);
|
|
1719
|
+
const refBox = this.safeBBox(refEl);
|
|
1720
|
+
// Use reference width and actual label height
|
|
1721
|
+
const widthForFit = refBox.width || labelBox.width || 1;
|
|
1722
|
+
const heightForFit = labelBox.height || refBox.height || 1;
|
|
1723
|
+
const s = Math.min(boxWidth / widthForFit, boxHeight / heightForFit) *
|
|
1724
|
+
this.baselineSafety();
|
|
1725
|
+
return `translate(${cx},${cy}) scale(${s})`;
|
|
1726
|
+
}, ...(ngDevMode ? [{ debugName: "valueTransform" }] : []));
|
|
1727
|
+
/** Guarded getBBox that avoids 0/NaN on detached or invisible nodes. */
|
|
1728
|
+
safeBBox(node) {
|
|
1729
|
+
try {
|
|
1730
|
+
const box = node.getBBox();
|
|
1731
|
+
// Firefox/Safari can occasionally return 0 when text hasn’t painted yet; fall back to a rough estimate.
|
|
1732
|
+
if (box && (box.width > 0 || box.height > 0))
|
|
1733
|
+
return box;
|
|
1734
|
+
}
|
|
1735
|
+
catch {
|
|
1736
|
+
/* ignore */
|
|
1737
|
+
}
|
|
1738
|
+
// Fallback guess to avoid divide-by-zero (tuned small; will get corrected next tick)
|
|
1739
|
+
return new DOMRect(0, 0, 1, 1);
|
|
1740
|
+
}
|
|
1741
|
+
// Responsive Size and Thickness Calculations
|
|
1742
|
+
/**
|
|
1743
|
+
* The effective gauge diameter, accounting for container sizing and manual size input.
|
|
1744
|
+
* Priority: containerSize (when fitToContainer=true) > manual size input
|
|
1745
|
+
* @returns Effective diameter in pixels
|
|
1746
|
+
*
|
|
1747
|
+
* @example
|
|
1748
|
+
* // Fixed size mode
|
|
1749
|
+
* fitToContainer=false, size=300 → effectiveSize=300
|
|
1750
|
+
*
|
|
1751
|
+
* // Container responsive mode
|
|
1752
|
+
* fitToContainer=true, container=400px wide → effectiveSize=380 (minus padding)
|
|
1753
|
+
*/
|
|
1754
|
+
effectiveSize = computed(() => {
|
|
1755
|
+
const containerDiameter = this.containerSize();
|
|
1756
|
+
if (this.fitToContainer() && containerDiameter !== null) {
|
|
1757
|
+
return containerDiameter;
|
|
1758
|
+
}
|
|
1759
|
+
return this.size();
|
|
1760
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveSize" }] : []));
|
|
1761
|
+
/**
|
|
1762
|
+
* Base thickness calculated from effective size for proportional scaling.
|
|
1763
|
+
* Only used when responsiveMode is enabled.
|
|
1764
|
+
* Formula: baseThickness = effectiveSize / sizeToThicknessRatio
|
|
1765
|
+
* @returns Base thickness in pixels, or 0 when responsiveMode is false
|
|
1766
|
+
*
|
|
1767
|
+
* @example
|
|
1768
|
+
* // effectiveSize=300, sizeToThicknessRatio=12
|
|
1769
|
+
* baseThickness = 300/12 = 25px
|
|
1770
|
+
* // Total ring thickness = 25 × 4.5 = 112.5px (37.5% of diameter)
|
|
1771
|
+
*/
|
|
1772
|
+
baseThickness = computed(() => {
|
|
1773
|
+
if (!this.responsiveMode())
|
|
1774
|
+
return 0;
|
|
1775
|
+
return this.effectiveSize() / this.sizeToThicknessRatio();
|
|
1776
|
+
}, ...(ngDevMode ? [{ debugName: "baseThickness" }] : []));
|
|
1777
|
+
/**
|
|
1778
|
+
* Effective outer ring thickness, supporting both manual and responsive modes.
|
|
1779
|
+
* - Responsive mode: baseThickness × responsiveProportions.outer
|
|
1780
|
+
* - Manual mode: outerThickness input value
|
|
1781
|
+
* @returns Outer ring thickness in pixels
|
|
1782
|
+
*/
|
|
1783
|
+
effectiveOuterThickness = computed(() => {
|
|
1784
|
+
if (this.responsiveMode()) {
|
|
1785
|
+
return this.baseThickness() * this.responsiveProportions().outer;
|
|
1786
|
+
}
|
|
1787
|
+
return this.outerThickness();
|
|
1788
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveOuterThickness" }] : []));
|
|
1789
|
+
/**
|
|
1790
|
+
* Effective inner ring thickness, supporting both manual and responsive modes.
|
|
1791
|
+
* - Responsive mode: baseThickness × responsiveProportions.inner
|
|
1792
|
+
* - Manual mode: innerThickness input value
|
|
1793
|
+
* @returns Inner ring thickness in pixels
|
|
1794
|
+
*/
|
|
1795
|
+
effectiveInnerThickness = computed(() => {
|
|
1796
|
+
if (this.responsiveMode()) {
|
|
1797
|
+
return this.baseThickness() * this.responsiveProportions().inner;
|
|
1798
|
+
}
|
|
1799
|
+
return this.innerThickness();
|
|
1800
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveInnerThickness" }] : []));
|
|
1801
|
+
/**
|
|
1802
|
+
* Effective gap between rings, supporting both manual and responsive modes.
|
|
1803
|
+
* - Responsive mode: baseThickness × responsiveProportions.gap
|
|
1804
|
+
* - Manual mode: gap input value
|
|
1805
|
+
* @returns Gap between rings in pixels
|
|
1806
|
+
*/
|
|
1807
|
+
effectiveGap = computed(() => {
|
|
1808
|
+
if (this.responsiveMode()) {
|
|
1809
|
+
return this.baseThickness() * this.responsiveProportions().gap;
|
|
1810
|
+
}
|
|
1811
|
+
return this.gap();
|
|
1812
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveGap" }] : []));
|
|
1813
|
+
// SVG Layout Calculations
|
|
1814
|
+
svgPadding = computed(() => this.effectiveOuterThickness() / 2, ...(ngDevMode ? [{ debugName: "svgPadding" }] : []));
|
|
1815
|
+
svgWidth = computed(() => this.effectiveSize() + this.effectiveOuterThickness(), ...(ngDevMode ? [{ debugName: "svgWidth" }] : []));
|
|
1816
|
+
svgHeight = computed(() => Math.ceil(this.effectiveSize() / 2 + this.effectiveOuterThickness() / 2), ...(ngDevMode ? [{ debugName: "svgHeight" }] : []));
|
|
1817
|
+
centerX = computed(() => this.effectiveSize() / 2 + this.effectiveOuterThickness() / 2, ...(ngDevMode ? [{ debugName: "centerX" }] : []));
|
|
1818
|
+
centerY = computed(() => this.effectiveSize() / 2 + this.effectiveOuterThickness() / 2, ...(ngDevMode ? [{ debugName: "centerY" }] : []));
|
|
1819
|
+
/**
|
|
1820
|
+
* If a string is provided, we measure it and allocate space for that width.
|
|
1821
|
+
* If a number is provided, we build a string of that many `referenceGlyph`s.
|
|
1822
|
+
* If omitted, we fall back to measuring the actual label.
|
|
1823
|
+
*/
|
|
1824
|
+
labelReference = input(undefined, ...(ngDevMode ? [{ debugName: "labelReference" }] : []));
|
|
1825
|
+
/** Glyph to repeat when labelReference is a number (defaults to '0'). */
|
|
1826
|
+
referenceGlyph = input('0', ...(ngDevMode ? [{ debugName: "referenceGlyph" }] : []));
|
|
1827
|
+
/** Extra breathing room inside the inner semicircle box (in px). */
|
|
1828
|
+
labelPadding = input(4, ...(ngDevMode ? [{ debugName: "labelPadding" }] : []));
|
|
1829
|
+
/** Safety multiplier to avoid clipping ascenders/descenders. */
|
|
1830
|
+
baselineSafety = input(0.95, ...(ngDevMode ? [{ debugName: "baselineSafety" }] : []));
|
|
1831
|
+
outerRadius = computed(() => this.effectiveSize() / 2, ...(ngDevMode ? [{ debugName: "outerRadius" }] : []));
|
|
1832
|
+
innerRadius = computed(() => this.outerRadius() -
|
|
1833
|
+
this.effectiveOuterThickness() / 2 -
|
|
1834
|
+
this.effectiveGap(), ...(ngDevMode ? [{ debugName: "innerRadius" }] : []));
|
|
1835
|
+
legendOuterRadius = computed(() => this.outerRadius() -
|
|
1836
|
+
this.effectiveOuterThickness() / 2 -
|
|
1837
|
+
this.effectiveGap() -
|
|
1838
|
+
this.effectiveInnerThickness() / 2, ...(ngDevMode ? [{ debugName: "legendOuterRadius" }] : []));
|
|
1839
|
+
legendInnerRadius = computed(() => this.legendOuterRadius() - this.effectiveInnerThickness(), ...(ngDevMode ? [{ debugName: "legendInnerRadius" }] : []));
|
|
1840
|
+
startAngle = -180;
|
|
1841
|
+
endAngle = 0;
|
|
1842
|
+
clampedValue = computed(() => this.clamp(this.value(), this.min(), this.max()), ...(ngDevMode ? [{ debugName: "clampedValue" }] : []));
|
|
1843
|
+
percentage = computed(() => {
|
|
1844
|
+
const range = this.max() - this.min();
|
|
1845
|
+
if (range === 0)
|
|
1846
|
+
return 0;
|
|
1847
|
+
return (this.clampedValue() - this.min()) / range;
|
|
1848
|
+
}, ...(ngDevMode ? [{ debugName: "percentage" }] : []));
|
|
1849
|
+
percent = computed(() => Math.round(this.percentage() * 100), ...(ngDevMode ? [{ debugName: "percent" }] : []));
|
|
1850
|
+
defaultSegments = computed(() => {
|
|
1851
|
+
const minVal = this.min();
|
|
1852
|
+
const maxVal = this.max();
|
|
1853
|
+
const range = maxVal - minVal;
|
|
1854
|
+
return [
|
|
1855
|
+
{
|
|
1856
|
+
from: minVal,
|
|
1857
|
+
to: minVal + 0.6 * range,
|
|
1858
|
+
color: 'var(--gauge-value-critical, #dc2626)',
|
|
1859
|
+
},
|
|
1860
|
+
{
|
|
1861
|
+
from: minVal + 0.6 * range,
|
|
1862
|
+
to: minVal + 0.8 * range,
|
|
1863
|
+
color: 'var(--gauge-value-warning, #f59e0b)',
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
from: minVal + 0.8 * range,
|
|
1867
|
+
to: maxVal,
|
|
1868
|
+
color: 'var(--gauge-value-good, #10b981)',
|
|
1869
|
+
},
|
|
1870
|
+
];
|
|
1871
|
+
}, ...(ngDevMode ? [{ debugName: "defaultSegments" }] : []));
|
|
1872
|
+
actualSegments = computed(() => this.segments() || this.defaultSegments(), ...(ngDevMode ? [{ debugName: "actualSegments" }] : []));
|
|
1873
|
+
formattedLabel = computed(() => this.nf.format(this.clampedValue()), ...(ngDevMode ? [{ debugName: "formattedLabel" }] : []));
|
|
1874
|
+
valueColor = computed(() => {
|
|
1875
|
+
const v = this.clampedValue();
|
|
1876
|
+
const segs = this.actualSegments();
|
|
1877
|
+
for (const s of segs) {
|
|
1878
|
+
if (v >= s.from && v <= s.to)
|
|
1879
|
+
return s.color;
|
|
1880
|
+
}
|
|
1881
|
+
return segs.at(-1)?.color ?? 'var(--mat-sys-primary)';
|
|
1882
|
+
}, ...(ngDevMode ? [{ debugName: "valueColor" }] : []));
|
|
1883
|
+
backgroundArcPath = computed(() => this.createArcPath(this.outerRadius(), this.startAngle, this.endAngle), ...(ngDevMode ? [{ debugName: "backgroundArcPath" }] : []));
|
|
1884
|
+
segmentPaths = computed(() => {
|
|
1885
|
+
const segs = this.actualSegments();
|
|
1886
|
+
const minVal = this.min();
|
|
1887
|
+
const maxVal = this.max();
|
|
1888
|
+
const range = maxVal - minVal;
|
|
1889
|
+
if (!range)
|
|
1890
|
+
return [];
|
|
1891
|
+
const r = this.legendOuterRadius();
|
|
1892
|
+
const gapDeg = this.gapDegreesForRadius(this.segmentGapPx(), r);
|
|
1893
|
+
return segs
|
|
1894
|
+
.map((s, i) => {
|
|
1895
|
+
const startPct = this.clamp((s.from - minVal) / range, 0, 1);
|
|
1896
|
+
const endPct = this.clamp((s.to - minVal) / range, 0, 1);
|
|
1897
|
+
let a0 = this.angleForPercentage(startPct);
|
|
1898
|
+
let a1 = this.angleForPercentage(endPct);
|
|
1899
|
+
if (i > 0)
|
|
1900
|
+
a0 += gapDeg / 2;
|
|
1901
|
+
if (i < segs.length - 1)
|
|
1902
|
+
a1 -= gapDeg / 2;
|
|
1903
|
+
if (a1 <= a0)
|
|
1904
|
+
return null;
|
|
1905
|
+
return { path: this.createArcPath(r, a0, a1), color: s.color };
|
|
1906
|
+
})
|
|
1907
|
+
.filter((x) => !!x);
|
|
1908
|
+
}, ...(ngDevMode ? [{ debugName: "segmentPaths" }] : []));
|
|
1909
|
+
ariaLabel = computed(() => `${this.title()}: ${this.formattedLabel()} (range ${this.min()}–${this.max()})`, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
1910
|
+
clamp(v, min, max) {
|
|
1911
|
+
return Math.min(Math.max(v, min), max);
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Converts a percentage (0-1) to an angle position on the gauge arc.
|
|
1915
|
+
* The gauge spans from startAngle (-180°) to endAngle (0°), creating a semicircle.
|
|
1916
|
+
* @param p - Percentage value between 0 and 1
|
|
1917
|
+
* @returns Angle in degrees for the given percentage along the gauge arc
|
|
1918
|
+
* @example
|
|
1919
|
+
* angleForPercentage(0) => -180° (start of gauge)
|
|
1920
|
+
* angleForPercentage(0.5) => -90° (middle of gauge)
|
|
1921
|
+
* angleForPercentage(1) => 0° (end of gauge)
|
|
1922
|
+
*/
|
|
1923
|
+
angleForPercentage(p) {
|
|
1924
|
+
return this.startAngle + (this.endAngle - this.startAngle) * p;
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Converts polar coordinates (radius, angle) to Cartesian coordinates (x, y).
|
|
1928
|
+
* Uses standard trigonometric conversion where angle 0° points to the right (3 o'clock).
|
|
1929
|
+
* @param cx - Center X coordinate of the circle
|
|
1930
|
+
* @param cy - Center Y coordinate of the circle
|
|
1931
|
+
* @param r - Radius distance from center
|
|
1932
|
+
* @param angle - Angle in degrees (0° = right, 90° = down, 180° = left, -90° = up)
|
|
1933
|
+
* @returns Object with x and y Cartesian coordinates
|
|
1934
|
+
* @example
|
|
1935
|
+
* polarToCartesian(100, 100, 50, 0) => {x: 150, y: 100} // 3 o'clock
|
|
1936
|
+
* polarToCartesian(100, 100, 50, -90) => {x: 100, y: 50} // 12 o'clock
|
|
1937
|
+
*/
|
|
1938
|
+
polarToCartesian(cx, cy, r, angle) {
|
|
1939
|
+
const a = (angle * Math.PI) / 180;
|
|
1940
|
+
return { x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) };
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Creates an SVG path string for a circular arc segment.
|
|
1944
|
+
* Uses SVG arc path commands to draw an arc from start angle to end angle.
|
|
1945
|
+
* @param r - Radius of the arc
|
|
1946
|
+
* @param a0 - Starting angle in degrees
|
|
1947
|
+
* @param a1 - Ending angle in degrees
|
|
1948
|
+
* @returns SVG path string defining the arc
|
|
1949
|
+
* @example
|
|
1950
|
+
* createArcPath(50, -180, 0) => "M cx-50 cy A 50 50 0 1 1 cx+50 cy"
|
|
1951
|
+
* This creates a semicircle from left (-180°) to right (0°)
|
|
1952
|
+
*
|
|
1953
|
+
* SVG Arc Parameters:
|
|
1954
|
+
* - rx, ry: Radii (equal for circular arc)
|
|
1955
|
+
* - x-axis-rotation: 0 (no rotation for circles)
|
|
1956
|
+
* - large-arc-flag: 1 if arc > 180°, 0 otherwise
|
|
1957
|
+
* - sweep-flag: 1 for clockwise, 0 for counter-clockwise
|
|
1958
|
+
*/
|
|
1959
|
+
createArcPath(r, a0, a1) {
|
|
1960
|
+
const cx = this.centerX(), cy = this.centerY();
|
|
1961
|
+
const start = this.polarToCartesian(cx, cy, r, a0);
|
|
1962
|
+
const end = this.polarToCartesian(cx, cy, r, a1);
|
|
1963
|
+
const largeArc = Math.abs(a1 - a0) > 180 ? 1 : 0;
|
|
1964
|
+
const sweep = a1 > a0 ? 1 : 0;
|
|
1965
|
+
return `M ${start.x} ${start.y} A ${r} ${r} 0 ${largeArc} ${sweep} ${end.x} ${end.y}`;
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Calculates the angular gap in degrees needed for a specific pixel gap at a given radius.
|
|
1969
|
+
* Used to create visual separation between legend segments.
|
|
1970
|
+
* @param px - Desired gap size in pixels
|
|
1971
|
+
* @param r - Radius at which the gap will appear
|
|
1972
|
+
* @returns Gap size in degrees, clamped between 0° and 180°
|
|
1973
|
+
* @example
|
|
1974
|
+
* For a 4px gap on a radius of 100px:
|
|
1975
|
+
* Arc length = π * 100 = 314.16px (semicircle)
|
|
1976
|
+
* Degrees = 180 * (4 / 314.16) ≈ 2.3°
|
|
1977
|
+
*
|
|
1978
|
+
* Mathematical basis:
|
|
1979
|
+
* - Semicircle arc length = π * r
|
|
1980
|
+
* - Ratio of gap to semicircle = px / (π * r)
|
|
1981
|
+
* - Convert ratio to degrees by multiplying by 180°
|
|
1982
|
+
*/
|
|
1983
|
+
gapDegreesForRadius(px, r) {
|
|
1984
|
+
const semicircumference = Math.PI * r;
|
|
1985
|
+
return 180 * this.clamp(px / semicircumference, 0, 1);
|
|
1986
|
+
}
|
|
1987
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: RadialGaugeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1988
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.1", type: RadialGaugeComponent, isStandalone: true, selector: "ngx-radial-gauge", 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 }, segments: { classPropertyName: "segments", publicName: "segments", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, segmentGapPx: { classPropertyName: "segmentGapPx", publicName: "segmentGapPx", isSignal: true, isRequired: false, transformFunction: null }, hasBackground: { classPropertyName: "hasBackground", publicName: "hasBackground", isSignal: true, isRequired: false, transformFunction: null }, showValueLabel: { classPropertyName: "showValueLabel", publicName: "showValueLabel", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, fitToContainer: { classPropertyName: "fitToContainer", publicName: "fitToContainer", isSignal: true, isRequired: false, transformFunction: null }, containerPadding: { classPropertyName: "containerPadding", publicName: "containerPadding", isSignal: true, isRequired: false, transformFunction: null }, responsiveMode: { classPropertyName: "responsiveMode", publicName: "responsiveMode", isSignal: true, isRequired: false, transformFunction: null }, sizeToThicknessRatio: { classPropertyName: "sizeToThicknessRatio", publicName: "sizeToThicknessRatio", isSignal: true, isRequired: false, transformFunction: null }, responsiveProportions: { classPropertyName: "responsiveProportions", publicName: "responsiveProportions", isSignal: true, isRequired: false, transformFunction: null }, outerThickness: { classPropertyName: "outerThickness", publicName: "outerThickness", isSignal: true, isRequired: false, transformFunction: null }, innerThickness: { classPropertyName: "innerThickness", publicName: "innerThickness", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, labelReference: { classPropertyName: "labelReference", publicName: "labelReference", isSignal: true, isRequired: false, transformFunction: null }, referenceGlyph: { classPropertyName: "referenceGlyph", publicName: "referenceGlyph", isSignal: true, isRequired: false, transformFunction: null }, labelPadding: { classPropertyName: "labelPadding", publicName: "labelPadding", isSignal: true, isRequired: false, transformFunction: null }, baselineSafety: { classPropertyName: "baselineSafety", publicName: "baselineSafety", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "meter" }, properties: { "attr.aria-label": "ariaLabel()", "attr.aria-valuemin": "min()", "attr.aria-valuemax": "max()", "attr.aria-valuenow": "clampedValue()", "attr.aria-valuetext": "formattedLabel()", "attr.aria-labelledby": "titleId", "attr.aria-describedby": "descId", "class.fit-container": "fitToContainer()", "class.has-background": "hasBackground()" } }, viewQueries: [{ propertyName: "valueTextEl", first: true, predicate: ["valueText"], descendants: true, isSignal: true }, { propertyName: "valueGroupEl", first: true, predicate: ["valueGroup"], descendants: true, isSignal: true }, { propertyName: "refTextEl", first: true, predicate: ["refText"], descendants: true, isSignal: true }], ngImport: i0, template: "@let w = svgWidth(); @let h = svgHeight(); @let cy = centerY(); @let pct =\r\npercent();\r\n\r\n<svg\r\n [attr.width]=\"fitToContainer() ? null : w\"\r\n [attr.height]=\"fitToContainer() ? null : h\"\r\n [attr.viewBox]=\"'0 0 ' + w + ' ' + h\"\r\n [class.responsive]=\"fitToContainer()\"\r\n class=\"gauge-svg\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n>\r\n <title [attr.id]=\"titleId\">{{ title() }}</title>\r\n @if (description()) {\r\n <desc [attr.id]=\"descId\">{{ description() }}</desc>\r\n }\r\n\r\n <defs>\r\n <clipPath [attr.id]=\"clipId\">\r\n <!-- Give a tiny extra room equal to half of the outer stroke to avoid anti-alias cutoff at the baseline -->\r\n <rect\r\n x=\"0\"\r\n y=\"0\"\r\n [attr.width]=\"w\"\r\n [attr.height]=\"cy + effectiveOuterThickness() / 2\"\r\n />\r\n </clipPath>\r\n </defs>\r\n\r\n <g [attr.clip-path]=\"'url(#' + clipId + ')'\">\r\n <path\r\n [attr.d]=\"backgroundArcPath()\"\r\n pathLength=\"100\"\r\n fill=\"none\"\r\n class=\"gauge-background\"\r\n [attr.stroke-width]=\"effectiveOuterThickness()\"\r\n stroke-linecap=\"butt\"\r\n />\r\n\r\n <path\r\n [attr.d]=\"backgroundArcPath()\"\r\n pathLength=\"100\"\r\n fill=\"none\"\r\n class=\"gauge-value\"\r\n [attr.stroke]=\"valueColor()\"\r\n [attr.stroke-width]=\"effectiveOuterThickness()\"\r\n stroke-linecap=\"butt\"\r\n [attr.stroke-dasharray]=\"pct + ' 100'\"\r\n />\r\n\r\n @for (segment of segmentPaths(); track segment.path) {\r\n <path\r\n [attr.d]=\"segment.path\"\r\n fill=\"none\"\r\n [attr.stroke]=\"segment.color\"\r\n [attr.stroke-width]=\"effectiveInnerThickness()\"\r\n stroke-linecap=\"butt\"\r\n class=\"gauge-segment\"\r\n />\r\n }\r\n </g>\r\n\r\n @if (showValueLabel()) {\r\n <g #valueGroup [attr.transform]=\"valueTransform()\">\r\n <text\r\n #valueText\r\n class=\"gauge-value-text\"\r\n x=\"0\"\r\n y=\"0\"\r\n text-anchor=\"middle\"\r\n alignment-baseline=\"baseline\"\r\n dy=\"-0.75\"\r\n >\r\n {{ formattedLabel() }}\r\n </text>\r\n </g>\r\n }\r\n\r\n <!-- Hidden reference text used ONLY for width measurement -->\r\n <g style=\"visibility: hidden; pointer-events: none\" aria-hidden=\"true\">\r\n <text\r\n #refText\r\n x=\"0\"\r\n y=\"0\"\r\n text-anchor=\"start\"\r\n dominant-baseline=\"alphabetic\"\r\n >\r\n {{ referenceString() }}\r\n </text>\r\n </g>\r\n</svg>\r\n", styles: [":host{display:block;--gauge-outer-bg: var(--mat-sys-surface-variant, #e0e0e0);--gauge-value-good: var(--mat-sys-tertiary, #10b981);--gauge-value-warning: var(--mat-sys-secondary, #f59e0b);--gauge-value-critical: var(--mat-sys-error, #dc2626)}:host.fit-container{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.gauge-svg{display:block;margin-inline:auto;max-width:100%;height:auto;shape-rendering:geometricPrecision}.gauge-svg.responsive{max-width:100%;max-height:100%;width:auto;height:auto}.gauge-background{stroke:var(--gauge-outer-bg);transition:stroke .2s ease}.gauge-value{transition:stroke-dasharray .2s ease,stroke .2s ease}.gauge-segment{transition:stroke .2s ease;opacity:.9}.gauge-segment:hover{opacity:1}@media (prefers-reduced-motion: reduce){.gauge-value,.gauge-segment{transition:none}}@media (prefers-contrast: high){.gauge-background{stroke-width:2;stroke:var(--mat-sys-outline, #000000)}.gauge-segment{opacity:1}}@media (prefers-color-scheme: dark){:host{--gauge-outer-bg: #374151}}.gauge-value-text{fill:var(--mat-sys-on-surface-variant, #6c757d);font-family:var(--mat-sys-typescale-body-large-font, \"Roboto\", sans-serif);font-weight:var(--mat-sys-typescale-body-large-weight, 400);transition:fill var(--mat-sys-motion-duration-short2, .2s) var(--mat-sys-motion-easing-standard, ease)}:host.has-background .gauge-value-text{fill:var(--mat-sys-on-surface, #1f1f1f)}:host:hover .gauge-value-text{fill:var(--mat-sys-primary, #6750a4)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1413
1989
|
}
|
|
1414
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.
|
|
1990
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: RadialGaugeComponent, decorators: [{
|
|
1415
1991
|
type: Component,
|
|
1416
|
-
args: [{ selector: 'ngx-
|
|
1992
|
+
args: [{ selector: 'ngx-radial-gauge', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
1993
|
+
role: 'meter',
|
|
1994
|
+
'[attr.aria-label]': 'ariaLabel()',
|
|
1995
|
+
'[attr.aria-valuemin]': 'min()',
|
|
1996
|
+
'[attr.aria-valuemax]': 'max()',
|
|
1997
|
+
'[attr.aria-valuenow]': 'clampedValue()',
|
|
1998
|
+
'[attr.aria-valuetext]': 'formattedLabel()',
|
|
1999
|
+
'[attr.aria-labelledby]': 'titleId',
|
|
2000
|
+
'[attr.aria-describedby]': 'descId',
|
|
2001
|
+
'[class.fit-container]': 'fitToContainer()',
|
|
2002
|
+
'[class.has-background]': 'hasBackground()',
|
|
2003
|
+
}, template: "@let w = svgWidth(); @let h = svgHeight(); @let cy = centerY(); @let pct =\r\npercent();\r\n\r\n<svg\r\n [attr.width]=\"fitToContainer() ? null : w\"\r\n [attr.height]=\"fitToContainer() ? null : h\"\r\n [attr.viewBox]=\"'0 0 ' + w + ' ' + h\"\r\n [class.responsive]=\"fitToContainer()\"\r\n class=\"gauge-svg\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n>\r\n <title [attr.id]=\"titleId\">{{ title() }}</title>\r\n @if (description()) {\r\n <desc [attr.id]=\"descId\">{{ description() }}</desc>\r\n }\r\n\r\n <defs>\r\n <clipPath [attr.id]=\"clipId\">\r\n <!-- Give a tiny extra room equal to half of the outer stroke to avoid anti-alias cutoff at the baseline -->\r\n <rect\r\n x=\"0\"\r\n y=\"0\"\r\n [attr.width]=\"w\"\r\n [attr.height]=\"cy + effectiveOuterThickness() / 2\"\r\n />\r\n </clipPath>\r\n </defs>\r\n\r\n <g [attr.clip-path]=\"'url(#' + clipId + ')'\">\r\n <path\r\n [attr.d]=\"backgroundArcPath()\"\r\n pathLength=\"100\"\r\n fill=\"none\"\r\n class=\"gauge-background\"\r\n [attr.stroke-width]=\"effectiveOuterThickness()\"\r\n stroke-linecap=\"butt\"\r\n />\r\n\r\n <path\r\n [attr.d]=\"backgroundArcPath()\"\r\n pathLength=\"100\"\r\n fill=\"none\"\r\n class=\"gauge-value\"\r\n [attr.stroke]=\"valueColor()\"\r\n [attr.stroke-width]=\"effectiveOuterThickness()\"\r\n stroke-linecap=\"butt\"\r\n [attr.stroke-dasharray]=\"pct + ' 100'\"\r\n />\r\n\r\n @for (segment of segmentPaths(); track segment.path) {\r\n <path\r\n [attr.d]=\"segment.path\"\r\n fill=\"none\"\r\n [attr.stroke]=\"segment.color\"\r\n [attr.stroke-width]=\"effectiveInnerThickness()\"\r\n stroke-linecap=\"butt\"\r\n class=\"gauge-segment\"\r\n />\r\n }\r\n </g>\r\n\r\n @if (showValueLabel()) {\r\n <g #valueGroup [attr.transform]=\"valueTransform()\">\r\n <text\r\n #valueText\r\n class=\"gauge-value-text\"\r\n x=\"0\"\r\n y=\"0\"\r\n text-anchor=\"middle\"\r\n alignment-baseline=\"baseline\"\r\n dy=\"-0.75\"\r\n >\r\n {{ formattedLabel() }}\r\n </text>\r\n </g>\r\n }\r\n\r\n <!-- Hidden reference text used ONLY for width measurement -->\r\n <g style=\"visibility: hidden; pointer-events: none\" aria-hidden=\"true\">\r\n <text\r\n #refText\r\n x=\"0\"\r\n y=\"0\"\r\n text-anchor=\"start\"\r\n dominant-baseline=\"alphabetic\"\r\n >\r\n {{ referenceString() }}\r\n </text>\r\n </g>\r\n</svg>\r\n", styles: [":host{display:block;--gauge-outer-bg: var(--mat-sys-surface-variant, #e0e0e0);--gauge-value-good: var(--mat-sys-tertiary, #10b981);--gauge-value-warning: var(--mat-sys-secondary, #f59e0b);--gauge-value-critical: var(--mat-sys-error, #dc2626)}:host.fit-container{width:100%;height:100%;display:flex;align-items:center;justify-content:center}.gauge-svg{display:block;margin-inline:auto;max-width:100%;height:auto;shape-rendering:geometricPrecision}.gauge-svg.responsive{max-width:100%;max-height:100%;width:auto;height:auto}.gauge-background{stroke:var(--gauge-outer-bg);transition:stroke .2s ease}.gauge-value{transition:stroke-dasharray .2s ease,stroke .2s ease}.gauge-segment{transition:stroke .2s ease;opacity:.9}.gauge-segment:hover{opacity:1}@media (prefers-reduced-motion: reduce){.gauge-value,.gauge-segment{transition:none}}@media (prefers-contrast: high){.gauge-background{stroke-width:2;stroke:var(--mat-sys-outline, #000000)}.gauge-segment{opacity:1}}@media (prefers-color-scheme: dark){:host{--gauge-outer-bg: #374151}}.gauge-value-text{fill:var(--mat-sys-on-surface-variant, #6c757d);font-family:var(--mat-sys-typescale-body-large-font, \"Roboto\", sans-serif);font-weight:var(--mat-sys-typescale-body-large-weight, 400);transition:fill var(--mat-sys-motion-duration-short2, .2s) var(--mat-sys-motion-easing-standard, ease)}:host.has-background .gauge-value-text{fill:var(--mat-sys-on-surface, #1f1f1f)}:host:hover .gauge-value-text{fill:var(--mat-sys-primary, #6750a4)}\n"] }]
|
|
1417
2004
|
}], ctorParameters: () => [] });
|
|
1418
2005
|
|
|
2006
|
+
// radial-gauge-widget.metadata.ts
|
|
2007
|
+
const svgIcon = `
|
|
2008
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 55" fill="currentColor">
|
|
2009
|
+
<defs>
|
|
2010
|
+
<clipPath id="gauge-clip"><rect x="0" y="0" width="100" height="52"/></clipPath>
|
|
2011
|
+
|
|
2012
|
+
<!-- Outer arc geometry (radius 40, stroke 8) -->
|
|
2013
|
+
<path id="outerArc" d="M 10 50 A 40 40 0 0 1 90 50" pathLength="100"/>
|
|
2014
|
+
|
|
2015
|
+
<!-- Inner arc geometry (radius 31) -->
|
|
2016
|
+
<path id="innerArc" d="M 19 50 A 31 31 0 0 1 81 50" pathLength="100"/>
|
|
2017
|
+
</defs>
|
|
2018
|
+
|
|
2019
|
+
<g clip-path="url(#gauge-clip)" stroke="currentColor" fill="none" stroke-linecap="butt">
|
|
2020
|
+
<!-- Outer background arc -->
|
|
2021
|
+
<use href="#outerArc" stroke-width="8" opacity="0.2"/>
|
|
2022
|
+
|
|
2023
|
+
<!-- Value arc: 65% -->
|
|
2024
|
+
<use href="#outerArc" stroke-width="8" stroke-dasharray="65 100"/>
|
|
2025
|
+
|
|
2026
|
+
<!-- Inner legend segments (single geometry with dash windows) -->
|
|
2027
|
+
<!-- 0–60% -->
|
|
2028
|
+
<use href="#innerArc" stroke-width="4" opacity="0.2"
|
|
2029
|
+
stroke-dasharray="60 100" stroke-dashoffset="0"/>
|
|
2030
|
+
<!-- 60–80% -->
|
|
2031
|
+
<use href="#innerArc" stroke-width="4" opacity="0.4"
|
|
2032
|
+
stroke-dasharray="20 100" stroke-dashoffset="60"/>
|
|
2033
|
+
<!-- 0–100% (full half-circle), same color as value arc -->
|
|
2034
|
+
<use href="#innerArc" stroke-width="4"
|
|
2035
|
+
stroke-dasharray="100 100" stroke-dashoffset="0"/>
|
|
2036
|
+
<!-- (Alternatively, you can omit dash attributes entirely on this one:
|
|
2037
|
+
<use href="#innerArc" stroke-width="4"/> ) -->
|
|
2038
|
+
</g>
|
|
2039
|
+
</svg>
|
|
2040
|
+
`;
|
|
2041
|
+
|
|
2042
|
+
class RadialGaugeStateDialogComponent {
|
|
2043
|
+
data = inject(MAT_DIALOG_DATA);
|
|
2044
|
+
dialogRef = inject((MatDialogRef));
|
|
2045
|
+
localState = {
|
|
2046
|
+
value: this.data.value ?? 50,
|
|
2047
|
+
colorProfile: this.data.colorProfile ?? 'dynamic',
|
|
2048
|
+
active: this.data.active ?? false,
|
|
2049
|
+
hasBackground: this.data.hasBackground ?? true,
|
|
2050
|
+
showValueLabel: this.data.showValueLabel ?? true,
|
|
2051
|
+
};
|
|
2052
|
+
onCancel() {
|
|
2053
|
+
this.dialogRef.close();
|
|
2054
|
+
}
|
|
2055
|
+
onSave() {
|
|
2056
|
+
this.dialogRef.close(this.localState);
|
|
2057
|
+
}
|
|
2058
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: RadialGaugeStateDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2059
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.2.1", type: RadialGaugeStateDialogComponent, isStandalone: true, selector: "lib-radial-gauge-state-dialog", ngImport: i0, template: `
|
|
2060
|
+
<h2 mat-dialog-title>Radial Gauge Settings</h2>
|
|
2061
|
+
<mat-dialog-content>
|
|
2062
|
+
<mat-form-field>
|
|
2063
|
+
<mat-label>Value (0-100)</mat-label>
|
|
2064
|
+
<input
|
|
2065
|
+
matInput
|
|
2066
|
+
type="number"
|
|
2067
|
+
[(ngModel)]="localState.value"
|
|
2068
|
+
min="0"
|
|
2069
|
+
max="100"
|
|
2070
|
+
/>
|
|
2071
|
+
</mat-form-field>
|
|
2072
|
+
|
|
2073
|
+
<div class="section">
|
|
2074
|
+
<h4>Color Profile</h4>
|
|
2075
|
+
<mat-radio-group [(ngModel)]="localState.colorProfile">
|
|
2076
|
+
<mat-radio-button value="dynamic">Dynamic (Theme Colors)</mat-radio-button>
|
|
2077
|
+
<mat-radio-button value="static">Static (Performance Colors)</mat-radio-button>
|
|
2078
|
+
</mat-radio-group>
|
|
2079
|
+
</div>
|
|
2080
|
+
|
|
2081
|
+
<div class="toggle-section">
|
|
2082
|
+
<mat-slide-toggle [(ngModel)]="localState.active">
|
|
2083
|
+
Active Display
|
|
2084
|
+
</mat-slide-toggle>
|
|
2085
|
+
<p class="toggle-description">Display live gauge instead of passive icon</p>
|
|
2086
|
+
</div>
|
|
2087
|
+
|
|
2088
|
+
<div class="toggle-section">
|
|
2089
|
+
<mat-slide-toggle [(ngModel)]="localState.hasBackground">
|
|
2090
|
+
Background
|
|
2091
|
+
</mat-slide-toggle>
|
|
2092
|
+
<p class="toggle-description">Add a background color to the widget</p>
|
|
2093
|
+
</div>
|
|
2094
|
+
|
|
2095
|
+
<div class="toggle-section">
|
|
2096
|
+
<mat-slide-toggle [(ngModel)]="localState.showValueLabel">
|
|
2097
|
+
Show Value Label
|
|
2098
|
+
</mat-slide-toggle>
|
|
2099
|
+
<p class="toggle-description">Display numeric value in gauge center</p>
|
|
2100
|
+
</div>
|
|
2101
|
+
</mat-dialog-content>
|
|
2102
|
+
|
|
2103
|
+
<mat-dialog-actions align="end">
|
|
2104
|
+
<button mat-button (click)="onCancel()">Cancel</button>
|
|
2105
|
+
<button mat-flat-button (click)="onSave()">Save</button>
|
|
2106
|
+
</mat-dialog-actions>
|
|
2107
|
+
`, isInline: true, styles: ["mat-dialog-content{display:block;overflow-y:auto;overflow-x:hidden}mat-form-field{width:100%;display:block;margin-bottom:1rem}.section{margin-bottom:1.5rem}.section h4{margin:0 0 .5rem;font-size:.875rem;font-weight:500;color:var(--mat-sys-on-surface, #1f1f1f)}mat-radio-group{display:flex;flex-direction:column;gap:.5rem}.toggle-section{display:flex;align-items:center;gap:.75rem;margin-bottom:.5rem}.toggle-description{margin:0;flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i2.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i2.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i2.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i3$1.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i3$1.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
2108
|
+
}
|
|
2109
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: RadialGaugeStateDialogComponent, decorators: [{
|
|
2110
|
+
type: Component,
|
|
2111
|
+
args: [{ selector: 'lib-radial-gauge-state-dialog', standalone: true, imports: [
|
|
2112
|
+
CommonModule,
|
|
2113
|
+
MatDialogModule,
|
|
2114
|
+
MatButtonModule,
|
|
2115
|
+
MatFormFieldModule,
|
|
2116
|
+
MatInputModule,
|
|
2117
|
+
MatSlideToggleModule,
|
|
2118
|
+
MatRadioModule,
|
|
2119
|
+
FormsModule,
|
|
2120
|
+
], template: `
|
|
2121
|
+
<h2 mat-dialog-title>Radial Gauge Settings</h2>
|
|
2122
|
+
<mat-dialog-content>
|
|
2123
|
+
<mat-form-field>
|
|
2124
|
+
<mat-label>Value (0-100)</mat-label>
|
|
2125
|
+
<input
|
|
2126
|
+
matInput
|
|
2127
|
+
type="number"
|
|
2128
|
+
[(ngModel)]="localState.value"
|
|
2129
|
+
min="0"
|
|
2130
|
+
max="100"
|
|
2131
|
+
/>
|
|
2132
|
+
</mat-form-field>
|
|
2133
|
+
|
|
2134
|
+
<div class="section">
|
|
2135
|
+
<h4>Color Profile</h4>
|
|
2136
|
+
<mat-radio-group [(ngModel)]="localState.colorProfile">
|
|
2137
|
+
<mat-radio-button value="dynamic">Dynamic (Theme Colors)</mat-radio-button>
|
|
2138
|
+
<mat-radio-button value="static">Static (Performance Colors)</mat-radio-button>
|
|
2139
|
+
</mat-radio-group>
|
|
2140
|
+
</div>
|
|
2141
|
+
|
|
2142
|
+
<div class="toggle-section">
|
|
2143
|
+
<mat-slide-toggle [(ngModel)]="localState.active">
|
|
2144
|
+
Active Display
|
|
2145
|
+
</mat-slide-toggle>
|
|
2146
|
+
<p class="toggle-description">Display live gauge instead of passive icon</p>
|
|
2147
|
+
</div>
|
|
2148
|
+
|
|
2149
|
+
<div class="toggle-section">
|
|
2150
|
+
<mat-slide-toggle [(ngModel)]="localState.hasBackground">
|
|
2151
|
+
Background
|
|
2152
|
+
</mat-slide-toggle>
|
|
2153
|
+
<p class="toggle-description">Add a background color to the widget</p>
|
|
2154
|
+
</div>
|
|
2155
|
+
|
|
2156
|
+
<div class="toggle-section">
|
|
2157
|
+
<mat-slide-toggle [(ngModel)]="localState.showValueLabel">
|
|
2158
|
+
Show Value Label
|
|
2159
|
+
</mat-slide-toggle>
|
|
2160
|
+
<p class="toggle-description">Display numeric value in gauge center</p>
|
|
2161
|
+
</div>
|
|
2162
|
+
</mat-dialog-content>
|
|
2163
|
+
|
|
2164
|
+
<mat-dialog-actions align="end">
|
|
2165
|
+
<button mat-button (click)="onCancel()">Cancel</button>
|
|
2166
|
+
<button mat-flat-button (click)="onSave()">Save</button>
|
|
2167
|
+
</mat-dialog-actions>
|
|
2168
|
+
`, styles: ["mat-dialog-content{display:block;overflow-y:auto;overflow-x:hidden}mat-form-field{width:100%;display:block;margin-bottom:1rem}.section{margin-bottom:1.5rem}.section h4{margin:0 0 .5rem;font-size:.875rem;font-weight:500;color:var(--mat-sys-on-surface, #1f1f1f)}mat-radio-group{display:flex;flex-direction:column;gap:.5rem}.toggle-section{display:flex;align-items:center;gap:.75rem;margin-bottom:.5rem}.toggle-description{margin:0;flex:1}\n"] }]
|
|
2169
|
+
}] });
|
|
2170
|
+
|
|
2171
|
+
// radial-gauge-widget.component.ts
|
|
2172
|
+
class RadialGaugeWidgetComponent {
|
|
2173
|
+
static metadata = {
|
|
2174
|
+
widgetTypeid: '@default/radial-gauge-widget',
|
|
2175
|
+
name: 'Radial Gauge',
|
|
2176
|
+
description: 'A semi-circular gauge indicator',
|
|
2177
|
+
svgIcon,
|
|
2178
|
+
};
|
|
2179
|
+
#dialog = inject(MatDialog);
|
|
2180
|
+
#sanitizer = inject(DomSanitizer);
|
|
2181
|
+
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon);
|
|
2182
|
+
state = signal({
|
|
2183
|
+
value: 50,
|
|
2184
|
+
colorProfile: 'dynamic',
|
|
2185
|
+
active: false,
|
|
2186
|
+
hasBackground: true,
|
|
2187
|
+
showValueLabel: true,
|
|
2188
|
+
}, ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
2189
|
+
segments = computed(() => {
|
|
2190
|
+
const profile = this.state().colorProfile || 'dynamic';
|
|
2191
|
+
if (profile === 'static') {
|
|
2192
|
+
// Static performance segments (like CPU usage example)
|
|
2193
|
+
return [
|
|
2194
|
+
{ from: 0, to: 25, color: '#dc2626' }, // Poor - red
|
|
2195
|
+
{ from: 25, to: 50, color: '#f59e0b' }, // Fair - orange
|
|
2196
|
+
{ from: 50, to: 75, color: '#3b82f6' }, // Good - blue
|
|
2197
|
+
{ from: 75, to: 100, color: '#10b981' }, // Excellent - green
|
|
2198
|
+
];
|
|
2199
|
+
}
|
|
2200
|
+
else {
|
|
2201
|
+
// Dynamic theme-aware segments (like demo gauge preview)
|
|
2202
|
+
return [
|
|
2203
|
+
{ from: 0, to: 60, color: 'var(--mat-sys-error)' },
|
|
2204
|
+
{ from: 60, to: 80, color: 'var(--mat-sys-secondary)' },
|
|
2205
|
+
{ from: 80, to: 100, color: 'var(--mat-sys-tertiary)' },
|
|
2206
|
+
];
|
|
2207
|
+
}
|
|
2208
|
+
}, ...(ngDevMode ? [{ debugName: "segments" }] : []));
|
|
2209
|
+
dashboardSetState(state) {
|
|
2210
|
+
if (state) {
|
|
2211
|
+
this.state.update((current) => ({
|
|
2212
|
+
...current,
|
|
2213
|
+
...state,
|
|
2214
|
+
}));
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
dashboardGetState() {
|
|
2218
|
+
return this.state();
|
|
2219
|
+
}
|
|
2220
|
+
dashboardEditState() {
|
|
2221
|
+
const dialogRef = this.#dialog.open(RadialGaugeStateDialogComponent, {
|
|
2222
|
+
data: this.state(),
|
|
2223
|
+
width: '400px',
|
|
2224
|
+
maxWidth: '90vw',
|
|
2225
|
+
disableClose: false,
|
|
2226
|
+
autoFocus: false,
|
|
2227
|
+
});
|
|
2228
|
+
dialogRef.afterClosed().subscribe((result) => {
|
|
2229
|
+
if (result) {
|
|
2230
|
+
this.state.set(result);
|
|
2231
|
+
}
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: RadialGaugeWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2235
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.1", type: RadialGaugeWidgetComponent, isStandalone: true, selector: "ngx-dashboard-radial-gauge-widget", ngImport: i0, template: "<!-- radial-gauge-widget.component.html -->\r\n<div class=\"widget-container\" [class.has-background]=\"state().hasBackground\">\r\n @if (state().active) {\r\n <!-- Active mode: Show live gauge -->\r\n <div class=\"gauge-container\">\r\n <ngx-radial-gauge\r\n [value]=\"state().value || 0\"\r\n [min]=\"0\"\r\n [max]=\"100\"\r\n [fitToContainer]=\"true\"\r\n [responsiveMode]=\"true\"\r\n [segments]=\"segments()\"\r\n [outerThickness]=\"24\"\r\n [innerThickness]=\"8\"\r\n [gap]=\"4\"\r\n [segmentGapPx]=\"2\"\r\n [labelReference]=\"'00000'\"\r\n [referenceGlyph]=\"'0'\"\r\n [hasBackground]=\"state().hasBackground || false\"\r\n [showValueLabel]=\"state().showValueLabel ?? true\"\r\n />\r\n </div>\r\n } @else {\r\n <!-- Passive mode: Show static icon -->\r\n <div class=\"icon-container\">\r\n <div class=\"svg-placeholder\" [innerHTML]=\"safeSvgIcon\"></div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;container-type:size;width:100%;height:100%;overflow:hidden}.widget-container{height:100%;width:100%;display:flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box;transition:background-color var(--mat-sys-motion-duration-medium2) var(--mat-sys-motion-easing-standard)}.widget-container.has-background{background-color:var(--mat-sys-surface-container-high);border-radius:4px}.gauge-container{height:100%;width:100%;display:flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box}ngx-radial-gauge{width:100%;height:100%;min-height:0;flex:1}.icon-container{height:100%;width:100%;display:flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box}.svg-placeholder{width:80%;height:80%;display:flex;align-items:center;justify-content:center;color:var(--mat-sys-on-surface-variant, #6c757d);transition:color .2s ease}.svg-placeholder :deep(svg){width:100%;height:100%;max-width:100px;max-height:100px;fill:currentColor}.has-background .svg-placeholder{color:var(--mat-sys-on-surface, #1f1f1f)}.widget-container:hover .svg-placeholder{color:var(--mat-sys-primary, #6750a4)}\n"], dependencies: [{ kind: "component", type: RadialGaugeComponent, selector: "ngx-radial-gauge", inputs: ["value", "min", "max", "segments", "title", "description", "segmentGapPx", "hasBackground", "showValueLabel", "size", "fitToContainer", "containerPadding", "responsiveMode", "sizeToThicknessRatio", "responsiveProportions", "outerThickness", "innerThickness", "gap", "labelReference", "referenceGlyph", "labelPadding", "baselineSafety"] }] });
|
|
2236
|
+
}
|
|
2237
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: RadialGaugeWidgetComponent, decorators: [{
|
|
2238
|
+
type: Component,
|
|
2239
|
+
args: [{ selector: 'ngx-dashboard-radial-gauge-widget', imports: [RadialGaugeComponent], template: "<!-- radial-gauge-widget.component.html -->\r\n<div class=\"widget-container\" [class.has-background]=\"state().hasBackground\">\r\n @if (state().active) {\r\n <!-- Active mode: Show live gauge -->\r\n <div class=\"gauge-container\">\r\n <ngx-radial-gauge\r\n [value]=\"state().value || 0\"\r\n [min]=\"0\"\r\n [max]=\"100\"\r\n [fitToContainer]=\"true\"\r\n [responsiveMode]=\"true\"\r\n [segments]=\"segments()\"\r\n [outerThickness]=\"24\"\r\n [innerThickness]=\"8\"\r\n [gap]=\"4\"\r\n [segmentGapPx]=\"2\"\r\n [labelReference]=\"'00000'\"\r\n [referenceGlyph]=\"'0'\"\r\n [hasBackground]=\"state().hasBackground || false\"\r\n [showValueLabel]=\"state().showValueLabel ?? true\"\r\n />\r\n </div>\r\n } @else {\r\n <!-- Passive mode: Show static icon -->\r\n <div class=\"icon-container\">\r\n <div class=\"svg-placeholder\" [innerHTML]=\"safeSvgIcon\"></div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;container-type:size;width:100%;height:100%;overflow:hidden}.widget-container{height:100%;width:100%;display:flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box;transition:background-color var(--mat-sys-motion-duration-medium2) var(--mat-sys-motion-easing-standard)}.widget-container.has-background{background-color:var(--mat-sys-surface-container-high);border-radius:4px}.gauge-container{height:100%;width:100%;display:flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box}ngx-radial-gauge{width:100%;height:100%;min-height:0;flex:1}.icon-container{height:100%;width:100%;display:flex;align-items:center;justify-content:center;position:relative;box-sizing:border-box}.svg-placeholder{width:80%;height:80%;display:flex;align-items:center;justify-content:center;color:var(--mat-sys-on-surface-variant, #6c757d);transition:color .2s ease}.svg-placeholder :deep(svg){width:100%;height:100%;max-width:100px;max-height:100px;fill:currentColor}.has-background .svg-placeholder{color:var(--mat-sys-on-surface, #1f1f1f)}.widget-container:hover .svg-placeholder{color:var(--mat-sys-primary, #6750a4)}\n"] }]
|
|
2240
|
+
}] });
|
|
2241
|
+
|
|
1419
2242
|
/*
|
|
1420
2243
|
* Public API Surface of ngx-dashboard-widgets
|
|
1421
2244
|
*/
|
|
@@ -1424,5 +2247,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
1424
2247
|
* Generated bundle index. Do not edit.
|
|
1425
2248
|
*/
|
|
1426
2249
|
|
|
1427
|
-
export { ArrowWidgetComponent, ClockWidgetComponent, LabelWidgetComponent, ResponsiveTextDirective };
|
|
2250
|
+
export { ArrowWidgetComponent, ClockWidgetComponent, LabelWidgetComponent, RadialGaugeComponent, RadialGaugeWidgetComponent, ResponsiveTextDirective };
|
|
1428
2251
|
//# sourceMappingURL=dragonworks-ngx-dashboard-widgets.mjs.map
|