@dragonworks/ngx-dashboard-widgets 20.0.4 → 20.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ng-package.json +7 -0
- package/package.json +31 -42
- package/src/lib/arrow-widget/arrow-state-dialog.component.ts +187 -0
- package/src/lib/arrow-widget/arrow-widget.component.html +9 -0
- package/src/lib/arrow-widget/arrow-widget.component.scss +52 -0
- package/src/lib/arrow-widget/arrow-widget.component.ts +78 -0
- package/src/lib/arrow-widget/arrow-widget.metadata.ts +3 -0
- package/src/lib/clock-widget/analog-clock/analog-clock.component.html +66 -0
- package/src/lib/clock-widget/analog-clock/analog-clock.component.scss +103 -0
- package/src/lib/clock-widget/analog-clock/analog-clock.component.ts +120 -0
- package/src/lib/clock-widget/clock-state-dialog.component.ts +170 -0
- package/src/lib/clock-widget/clock-widget.component.html +16 -0
- package/src/lib/clock-widget/clock-widget.component.scss +160 -0
- package/src/lib/clock-widget/clock-widget.component.ts +87 -0
- package/src/lib/clock-widget/clock-widget.metadata.ts +42 -0
- package/src/lib/clock-widget/digital-clock/__tests__/digital-clock.component.spec.ts +276 -0
- package/src/lib/clock-widget/digital-clock/digital-clock.component.html +1 -0
- package/src/lib/clock-widget/digital-clock/digital-clock.component.scss +43 -0
- package/src/lib/clock-widget/digital-clock/digital-clock.component.ts +105 -0
- package/src/lib/directives/__tests__/responsive-text.directive.spec.ts +906 -0
- package/src/lib/directives/responsive-text.directive.ts +334 -0
- package/src/lib/label-widget/__tests__/label-widget.component.spec.ts +539 -0
- package/src/lib/label-widget/label-state-dialog.component.ts +385 -0
- package/src/lib/label-widget/label-widget.component.html +21 -0
- package/src/lib/label-widget/label-widget.component.scss +112 -0
- package/src/lib/label-widget/label-widget.component.ts +96 -0
- package/src/lib/label-widget/label-widget.metadata.ts +3 -0
- package/src/public-api.ts +7 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +14 -0
- package/fesm2022/dragonworks-ngx-dashboard-widgets.mjs +0 -1255
- package/fesm2022/dragonworks-ngx-dashboard-widgets.mjs.map +0 -1
- package/index.d.ts +0 -147
|
@@ -1,1255 +0,0 @@
|
|
|
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';
|
|
3
|
-
import { DomSanitizer } from '@angular/platform-browser';
|
|
4
|
-
import * as i2 from '@angular/material/dialog';
|
|
5
|
-
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule, MatDialog } from '@angular/material/dialog';
|
|
6
|
-
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
|
7
|
-
import * as i1 from '@angular/forms';
|
|
8
|
-
import { FormsModule } from '@angular/forms';
|
|
9
|
-
import * as i3 from '@angular/material/button';
|
|
10
|
-
import { MatButtonModule } from '@angular/material/button';
|
|
11
|
-
import * as i4 from '@angular/material/form-field';
|
|
12
|
-
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
13
|
-
import * as i5 from '@angular/material/select';
|
|
14
|
-
import { MatSelectModule } from '@angular/material/select';
|
|
15
|
-
import * as i6 from '@angular/material/slider';
|
|
16
|
-
import { MatSliderModule } from '@angular/material/slider';
|
|
17
|
-
import * as i7 from '@angular/material/slide-toggle';
|
|
18
|
-
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
19
|
-
import * as i5$1 from '@angular/material/input';
|
|
20
|
-
import { MatInputModule } from '@angular/material/input';
|
|
21
|
-
import * as i3$1 from '@angular/material/radio';
|
|
22
|
-
import { MatRadioModule } from '@angular/material/radio';
|
|
23
|
-
|
|
24
|
-
// arrow-widget.metadata.ts
|
|
25
|
-
const svgIcon$2 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M320-120v-320H120l360-440 360 440H640v320H320Zm80-80h160v-320h111L480-754 289-520h111v320Zm80-320Z"/></svg>';
|
|
26
|
-
|
|
27
|
-
class ArrowStateDialogComponent {
|
|
28
|
-
data = inject(MAT_DIALOG_DATA);
|
|
29
|
-
dialogRef = inject((MatDialogRef));
|
|
30
|
-
// 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));
|
|
35
|
-
// Store original values for comparison
|
|
36
|
-
originalDirection = this.data.direction;
|
|
37
|
-
originalOpacity = this.data.opacity ?? 1;
|
|
38
|
-
originalHasBackground = this.data.hasBackground ?? true;
|
|
39
|
-
// Computed values
|
|
40
|
-
rotation = computed(() => {
|
|
41
|
-
const rotationMap = {
|
|
42
|
-
up: 0,
|
|
43
|
-
right: 90,
|
|
44
|
-
down: 180,
|
|
45
|
-
left: 270,
|
|
46
|
-
};
|
|
47
|
-
return rotationMap[this.direction()];
|
|
48
|
-
});
|
|
49
|
-
rotationTransform = computed(() => `rotate(${this.rotation()}deg)`);
|
|
50
|
-
directionName = computed(() => {
|
|
51
|
-
const nameMap = {
|
|
52
|
-
up: 'Up',
|
|
53
|
-
right: 'Right',
|
|
54
|
-
down: 'Down',
|
|
55
|
-
left: 'Left',
|
|
56
|
-
};
|
|
57
|
-
return nameMap[this.direction()];
|
|
58
|
-
});
|
|
59
|
-
hasChanged = computed(() => this.direction() !== this.originalDirection ||
|
|
60
|
-
this.opacity() !== this.originalOpacity ||
|
|
61
|
-
this.hasBackground() !== this.originalHasBackground);
|
|
62
|
-
formatOpacity(value) {
|
|
63
|
-
return Math.round(value * 100);
|
|
64
|
-
}
|
|
65
|
-
onBackgroundToggle(hasBackground) {
|
|
66
|
-
this.hasBackground.set(hasBackground);
|
|
67
|
-
this.transparentBackground.set(!hasBackground);
|
|
68
|
-
}
|
|
69
|
-
onCancel() {
|
|
70
|
-
this.dialogRef.close();
|
|
71
|
-
}
|
|
72
|
-
save() {
|
|
73
|
-
this.dialogRef.close({
|
|
74
|
-
direction: this.direction(),
|
|
75
|
-
opacity: this.opacity(),
|
|
76
|
-
hasBackground: this.hasBackground(),
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ArrowStateDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
80
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.6", type: ArrowStateDialogComponent, isStandalone: true, selector: "lib-arrow-state-dialog", ngImport: i0, template: `
|
|
81
|
-
<h2 mat-dialog-title>Arrow Settings</h2>
|
|
82
|
-
<mat-dialog-content>
|
|
83
|
-
<!-- Direction Selection -->
|
|
84
|
-
<mat-form-field appearance="outline" class="direction-field">
|
|
85
|
-
<mat-label>Arrow Direction</mat-label>
|
|
86
|
-
<mat-select
|
|
87
|
-
[value]="direction()"
|
|
88
|
-
(selectionChange)="direction.set($any($event.value))"
|
|
89
|
-
>
|
|
90
|
-
<mat-option value="up">Up</mat-option>
|
|
91
|
-
<mat-option value="right">Right</mat-option>
|
|
92
|
-
<mat-option value="down">Down</mat-option>
|
|
93
|
-
<mat-option value="left">Left</mat-option>
|
|
94
|
-
</mat-select>
|
|
95
|
-
</mat-form-field>
|
|
96
|
-
|
|
97
|
-
<!-- Opacity Slider -->
|
|
98
|
-
<div class="slider-field">
|
|
99
|
-
<div class="field-label">Opacity: {{ formatOpacity(opacity()) }}%</div>
|
|
100
|
-
<mat-slider [min]="0.1" [max]="1" [step]="0.1">
|
|
101
|
-
<input matSliderThumb [(ngModel)]="opacity" />
|
|
102
|
-
</mat-slider>
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
<!-- Background Toggle -->
|
|
106
|
-
<div class="toggle-field">
|
|
107
|
-
<mat-slide-toggle
|
|
108
|
-
[checked]="hasBackground()"
|
|
109
|
-
(change)="onBackgroundToggle($event.checked)">
|
|
110
|
-
Background
|
|
111
|
-
</mat-slide-toggle>
|
|
112
|
-
<span class="toggle-hint">Adds a background behind the arrow</span>
|
|
113
|
-
</div>
|
|
114
|
-
</mat-dialog-content>
|
|
115
|
-
|
|
116
|
-
<mat-dialog-actions align="end">
|
|
117
|
-
<button mat-button (click)="onCancel()">Cancel</button>
|
|
118
|
-
<button mat-flat-button (click)="save()" [disabled]="!hasChanged()">
|
|
119
|
-
Save
|
|
120
|
-
</button>
|
|
121
|
-
</mat-dialog-actions>
|
|
122
|
-
`, 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
|
-
}
|
|
124
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ArrowStateDialogComponent, decorators: [{
|
|
125
|
-
type: Component,
|
|
126
|
-
args: [{ selector: 'lib-arrow-state-dialog', standalone: true, imports: [
|
|
127
|
-
CommonModule,
|
|
128
|
-
FormsModule,
|
|
129
|
-
MatDialogModule,
|
|
130
|
-
MatButtonModule,
|
|
131
|
-
MatFormFieldModule,
|
|
132
|
-
MatSelectModule,
|
|
133
|
-
MatSliderModule,
|
|
134
|
-
MatSlideToggleModule,
|
|
135
|
-
], template: `
|
|
136
|
-
<h2 mat-dialog-title>Arrow Settings</h2>
|
|
137
|
-
<mat-dialog-content>
|
|
138
|
-
<!-- Direction Selection -->
|
|
139
|
-
<mat-form-field appearance="outline" class="direction-field">
|
|
140
|
-
<mat-label>Arrow Direction</mat-label>
|
|
141
|
-
<mat-select
|
|
142
|
-
[value]="direction()"
|
|
143
|
-
(selectionChange)="direction.set($any($event.value))"
|
|
144
|
-
>
|
|
145
|
-
<mat-option value="up">Up</mat-option>
|
|
146
|
-
<mat-option value="right">Right</mat-option>
|
|
147
|
-
<mat-option value="down">Down</mat-option>
|
|
148
|
-
<mat-option value="left">Left</mat-option>
|
|
149
|
-
</mat-select>
|
|
150
|
-
</mat-form-field>
|
|
151
|
-
|
|
152
|
-
<!-- Opacity Slider -->
|
|
153
|
-
<div class="slider-field">
|
|
154
|
-
<div class="field-label">Opacity: {{ formatOpacity(opacity()) }}%</div>
|
|
155
|
-
<mat-slider [min]="0.1" [max]="1" [step]="0.1">
|
|
156
|
-
<input matSliderThumb [(ngModel)]="opacity" />
|
|
157
|
-
</mat-slider>
|
|
158
|
-
</div>
|
|
159
|
-
|
|
160
|
-
<!-- Background Toggle -->
|
|
161
|
-
<div class="toggle-field">
|
|
162
|
-
<mat-slide-toggle
|
|
163
|
-
[checked]="hasBackground()"
|
|
164
|
-
(change)="onBackgroundToggle($event.checked)">
|
|
165
|
-
Background
|
|
166
|
-
</mat-slide-toggle>
|
|
167
|
-
<span class="toggle-hint">Adds a background behind the arrow</span>
|
|
168
|
-
</div>
|
|
169
|
-
</mat-dialog-content>
|
|
170
|
-
|
|
171
|
-
<mat-dialog-actions align="end">
|
|
172
|
-
<button mat-button (click)="onCancel()">Cancel</button>
|
|
173
|
-
<button mat-flat-button (click)="save()" [disabled]="!hasChanged()">
|
|
174
|
-
Save
|
|
175
|
-
</button>
|
|
176
|
-
</mat-dialog-actions>
|
|
177
|
-
`, 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"] }]
|
|
178
|
-
}] });
|
|
179
|
-
|
|
180
|
-
// arrow-widget.component.ts
|
|
181
|
-
class ArrowWidgetComponent {
|
|
182
|
-
static metadata = {
|
|
183
|
-
widgetTypeid: '@default/arrow-widget',
|
|
184
|
-
name: 'Arrow',
|
|
185
|
-
description: 'A generic arrow',
|
|
186
|
-
svgIcon: svgIcon$2,
|
|
187
|
-
};
|
|
188
|
-
#sanitizer = inject(DomSanitizer);
|
|
189
|
-
#dialog = inject(MatDialog);
|
|
190
|
-
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon$2);
|
|
191
|
-
state = signal({
|
|
192
|
-
direction: 'up',
|
|
193
|
-
opacity: 0.3,
|
|
194
|
-
hasBackground: true,
|
|
195
|
-
});
|
|
196
|
-
// Computed rotation
|
|
197
|
-
rotationAngle = computed(() => {
|
|
198
|
-
const rotationMap = {
|
|
199
|
-
up: 0,
|
|
200
|
-
right: 90,
|
|
201
|
-
down: 180,
|
|
202
|
-
left: 270,
|
|
203
|
-
};
|
|
204
|
-
return rotationMap[this.state().direction];
|
|
205
|
-
});
|
|
206
|
-
dashboardSetState(state) {
|
|
207
|
-
if (state) {
|
|
208
|
-
this.state.update((current) => ({
|
|
209
|
-
...current,
|
|
210
|
-
...state,
|
|
211
|
-
}));
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
dashboardGetState() {
|
|
215
|
-
return this.state();
|
|
216
|
-
}
|
|
217
|
-
dashboardEditState() {
|
|
218
|
-
const dialogRef = this.#dialog.open(ArrowStateDialogComponent, {
|
|
219
|
-
data: this.state(),
|
|
220
|
-
width: '400px',
|
|
221
|
-
maxWidth: '90vw',
|
|
222
|
-
disableClose: false,
|
|
223
|
-
autoFocus: false,
|
|
224
|
-
});
|
|
225
|
-
dialogRef.afterClosed().subscribe((result) => {
|
|
226
|
-
if (result) {
|
|
227
|
-
this.state.set(result);
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ArrowWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
232
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.6", 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;transform-origin:center center}.svg-placeholder ::ng-deep svg{width:100%;height:100%;display:block;fill:var(--mat-sys-on-surface-variant, #6c757d);transition:fill .2s ease}.has-background .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-on-surface, #1f1f1f)}.svg-wrapper:hover .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-primary, #6750a4)}\n"] });
|
|
233
|
-
}
|
|
234
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ArrowWidgetComponent, decorators: [{
|
|
235
|
-
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}.svg-placeholder ::ng-deep svg{width:100%;height:100%;display:block;fill:var(--mat-sys-on-surface-variant, #6c757d);transition:fill .2s ease}.has-background .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-on-surface, #1f1f1f)}.svg-wrapper:hover .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-primary, #6750a4)}\n"] }]
|
|
237
|
-
}] });
|
|
238
|
-
|
|
239
|
-
// label-widget.metadata.ts
|
|
240
|
-
const svgIcon$1 = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path 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
|
-
|
|
242
|
-
class LabelStateDialogComponent {
|
|
243
|
-
data = inject(MAT_DIALOG_DATA);
|
|
244
|
-
dialogRef = inject((MatDialogRef));
|
|
245
|
-
// 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);
|
|
254
|
-
// Store original values for comparison
|
|
255
|
-
originalLabel = this.data.label ?? '';
|
|
256
|
-
originalFontSize = this.data.fontSize ?? 16;
|
|
257
|
-
originalAlignment = this.data.alignment ?? 'center';
|
|
258
|
-
originalFontWeight = this.data.fontWeight ?? 'normal';
|
|
259
|
-
originalOpacity = this.data.opacity ?? 1;
|
|
260
|
-
originalHasBackground = this.data.hasBackground ?? true;
|
|
261
|
-
originalResponsive = this.data.responsive ?? false;
|
|
262
|
-
// Computed values
|
|
263
|
-
hasChanged = computed(() => this.label() !== this.originalLabel ||
|
|
264
|
-
this.fontSize() !== this.originalFontSize ||
|
|
265
|
-
this.alignment() !== this.originalAlignment ||
|
|
266
|
-
this.fontWeight() !== this.originalFontWeight ||
|
|
267
|
-
this.opacity() !== this.originalOpacity ||
|
|
268
|
-
this.hasBackground() !== this.originalHasBackground ||
|
|
269
|
-
this.responsive() !== this.originalResponsive);
|
|
270
|
-
formatOpacity(value) {
|
|
271
|
-
return Math.round(value * 100);
|
|
272
|
-
}
|
|
273
|
-
formatOpacitySlider = (value) => {
|
|
274
|
-
return `${Math.round(value * 100)}%`;
|
|
275
|
-
};
|
|
276
|
-
onBackgroundToggle(hasWhiteBackground) {
|
|
277
|
-
this.hasBackground.set(hasWhiteBackground);
|
|
278
|
-
this.transparentBackground.set(!hasWhiteBackground);
|
|
279
|
-
}
|
|
280
|
-
onCancel() {
|
|
281
|
-
this.dialogRef.close();
|
|
282
|
-
}
|
|
283
|
-
save() {
|
|
284
|
-
this.dialogRef.close({
|
|
285
|
-
label: this.label(),
|
|
286
|
-
fontSize: this.fontSize(),
|
|
287
|
-
alignment: this.alignment(),
|
|
288
|
-
fontWeight: this.fontWeight(),
|
|
289
|
-
opacity: this.opacity(),
|
|
290
|
-
hasBackground: this.hasBackground(),
|
|
291
|
-
responsive: this.responsive(),
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: LabelStateDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
295
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.6", type: LabelStateDialogComponent, isStandalone: true, selector: "lib-label-state-dialog", ngImport: i0, template: `
|
|
296
|
-
<h2 mat-dialog-title>Label Settings</h2>
|
|
297
|
-
<mat-dialog-content>
|
|
298
|
-
<mat-form-field appearance="outline" class="label-text-field">
|
|
299
|
-
<mat-label>Label Text</mat-label>
|
|
300
|
-
<input
|
|
301
|
-
matInput
|
|
302
|
-
type="text"
|
|
303
|
-
[value]="label()"
|
|
304
|
-
(input)="label.set($any($event.target).value)"
|
|
305
|
-
placeholder="Enter your label text..."
|
|
306
|
-
/>
|
|
307
|
-
</mat-form-field>
|
|
308
|
-
|
|
309
|
-
<!-- Responsive Text Toggle -->
|
|
310
|
-
<div class="toggle-section">
|
|
311
|
-
<mat-slide-toggle
|
|
312
|
-
[checked]="responsive()"
|
|
313
|
-
(change)="responsive.set($event.checked)">
|
|
314
|
-
Responsive Text
|
|
315
|
-
</mat-slide-toggle>
|
|
316
|
-
<span class="toggle-description"
|
|
317
|
-
>Automatically adjust text size to fit the widget</span
|
|
318
|
-
>
|
|
319
|
-
</div>
|
|
320
|
-
|
|
321
|
-
<div class="row-layout">
|
|
322
|
-
<mat-form-field appearance="outline">
|
|
323
|
-
<mat-label>Font Size (px)</mat-label>
|
|
324
|
-
<input
|
|
325
|
-
matInput
|
|
326
|
-
type="number"
|
|
327
|
-
[value]="fontSize()"
|
|
328
|
-
(input)="fontSize.set(+$any($event.target).value)"
|
|
329
|
-
[disabled]="responsive()"
|
|
330
|
-
min="8"
|
|
331
|
-
max="48"
|
|
332
|
-
placeholder="16"
|
|
333
|
-
/>
|
|
334
|
-
</mat-form-field>
|
|
335
|
-
|
|
336
|
-
<mat-form-field appearance="outline">
|
|
337
|
-
<mat-label>Alignment</mat-label>
|
|
338
|
-
<mat-select
|
|
339
|
-
[value]="alignment()"
|
|
340
|
-
(selectionChange)="alignment.set($any($event.value))"
|
|
341
|
-
>
|
|
342
|
-
<mat-option value="left">Left</mat-option>
|
|
343
|
-
<mat-option value="center">Center</mat-option>
|
|
344
|
-
<mat-option value="right">Right</mat-option>
|
|
345
|
-
</mat-select>
|
|
346
|
-
</mat-form-field>
|
|
347
|
-
</div>
|
|
348
|
-
|
|
349
|
-
<mat-form-field appearance="outline">
|
|
350
|
-
<mat-label>Font Weight</mat-label>
|
|
351
|
-
<mat-select
|
|
352
|
-
[value]="fontWeight()"
|
|
353
|
-
(selectionChange)="fontWeight.set($any($event.value))"
|
|
354
|
-
>
|
|
355
|
-
<mat-option value="normal">Normal</mat-option>
|
|
356
|
-
<mat-option value="bold">Bold</mat-option>
|
|
357
|
-
</mat-select>
|
|
358
|
-
</mat-form-field>
|
|
359
|
-
|
|
360
|
-
<!-- Opacity Slider -->
|
|
361
|
-
<div class="slider-section">
|
|
362
|
-
<div class="slider-label">Opacity: {{ formatOpacity(opacity()) }}%</div>
|
|
363
|
-
<mat-slider [min]="0.1" [max]="1" [step]="0.1">
|
|
364
|
-
<input matSliderThumb [(ngModel)]="opacity" />
|
|
365
|
-
</mat-slider>
|
|
366
|
-
</div>
|
|
367
|
-
|
|
368
|
-
<!-- Background Toggle -->
|
|
369
|
-
<div class="toggle-section">
|
|
370
|
-
<mat-slide-toggle
|
|
371
|
-
[checked]="!transparentBackground()"
|
|
372
|
-
(change)="onBackgroundToggle($event.checked)">
|
|
373
|
-
Background
|
|
374
|
-
</mat-slide-toggle>
|
|
375
|
-
<span class="toggle-description"
|
|
376
|
-
>Adds a background behind the text</span
|
|
377
|
-
>
|
|
378
|
-
</div>
|
|
379
|
-
</mat-dialog-content>
|
|
380
|
-
|
|
381
|
-
<mat-dialog-actions align="end">
|
|
382
|
-
<button mat-button (click)="onCancel()">Cancel</button>
|
|
383
|
-
<button mat-flat-button (click)="save()" [disabled]="!hasChanged()">
|
|
384
|
-
Save
|
|
385
|
-
</button>
|
|
386
|
-
</mat-dialog-actions>
|
|
387
|
-
`, 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}\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: 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"] }] });
|
|
388
|
-
}
|
|
389
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: LabelStateDialogComponent, decorators: [{
|
|
390
|
-
type: Component,
|
|
391
|
-
args: [{ selector: 'lib-label-state-dialog', standalone: true, imports: [
|
|
392
|
-
CommonModule,
|
|
393
|
-
FormsModule,
|
|
394
|
-
MatDialogModule,
|
|
395
|
-
MatButtonModule,
|
|
396
|
-
MatFormFieldModule,
|
|
397
|
-
MatInputModule,
|
|
398
|
-
MatSelectModule,
|
|
399
|
-
MatSliderModule,
|
|
400
|
-
MatSlideToggleModule, // Add this import
|
|
401
|
-
], template: `
|
|
402
|
-
<h2 mat-dialog-title>Label Settings</h2>
|
|
403
|
-
<mat-dialog-content>
|
|
404
|
-
<mat-form-field appearance="outline" class="label-text-field">
|
|
405
|
-
<mat-label>Label Text</mat-label>
|
|
406
|
-
<input
|
|
407
|
-
matInput
|
|
408
|
-
type="text"
|
|
409
|
-
[value]="label()"
|
|
410
|
-
(input)="label.set($any($event.target).value)"
|
|
411
|
-
placeholder="Enter your label text..."
|
|
412
|
-
/>
|
|
413
|
-
</mat-form-field>
|
|
414
|
-
|
|
415
|
-
<!-- Responsive Text Toggle -->
|
|
416
|
-
<div class="toggle-section">
|
|
417
|
-
<mat-slide-toggle
|
|
418
|
-
[checked]="responsive()"
|
|
419
|
-
(change)="responsive.set($event.checked)">
|
|
420
|
-
Responsive Text
|
|
421
|
-
</mat-slide-toggle>
|
|
422
|
-
<span class="toggle-description"
|
|
423
|
-
>Automatically adjust text size to fit the widget</span
|
|
424
|
-
>
|
|
425
|
-
</div>
|
|
426
|
-
|
|
427
|
-
<div class="row-layout">
|
|
428
|
-
<mat-form-field appearance="outline">
|
|
429
|
-
<mat-label>Font Size (px)</mat-label>
|
|
430
|
-
<input
|
|
431
|
-
matInput
|
|
432
|
-
type="number"
|
|
433
|
-
[value]="fontSize()"
|
|
434
|
-
(input)="fontSize.set(+$any($event.target).value)"
|
|
435
|
-
[disabled]="responsive()"
|
|
436
|
-
min="8"
|
|
437
|
-
max="48"
|
|
438
|
-
placeholder="16"
|
|
439
|
-
/>
|
|
440
|
-
</mat-form-field>
|
|
441
|
-
|
|
442
|
-
<mat-form-field appearance="outline">
|
|
443
|
-
<mat-label>Alignment</mat-label>
|
|
444
|
-
<mat-select
|
|
445
|
-
[value]="alignment()"
|
|
446
|
-
(selectionChange)="alignment.set($any($event.value))"
|
|
447
|
-
>
|
|
448
|
-
<mat-option value="left">Left</mat-option>
|
|
449
|
-
<mat-option value="center">Center</mat-option>
|
|
450
|
-
<mat-option value="right">Right</mat-option>
|
|
451
|
-
</mat-select>
|
|
452
|
-
</mat-form-field>
|
|
453
|
-
</div>
|
|
454
|
-
|
|
455
|
-
<mat-form-field appearance="outline">
|
|
456
|
-
<mat-label>Font Weight</mat-label>
|
|
457
|
-
<mat-select
|
|
458
|
-
[value]="fontWeight()"
|
|
459
|
-
(selectionChange)="fontWeight.set($any($event.value))"
|
|
460
|
-
>
|
|
461
|
-
<mat-option value="normal">Normal</mat-option>
|
|
462
|
-
<mat-option value="bold">Bold</mat-option>
|
|
463
|
-
</mat-select>
|
|
464
|
-
</mat-form-field>
|
|
465
|
-
|
|
466
|
-
<!-- Opacity Slider -->
|
|
467
|
-
<div class="slider-section">
|
|
468
|
-
<div class="slider-label">Opacity: {{ formatOpacity(opacity()) }}%</div>
|
|
469
|
-
<mat-slider [min]="0.1" [max]="1" [step]="0.1">
|
|
470
|
-
<input matSliderThumb [(ngModel)]="opacity" />
|
|
471
|
-
</mat-slider>
|
|
472
|
-
</div>
|
|
473
|
-
|
|
474
|
-
<!-- Background Toggle -->
|
|
475
|
-
<div class="toggle-section">
|
|
476
|
-
<mat-slide-toggle
|
|
477
|
-
[checked]="!transparentBackground()"
|
|
478
|
-
(change)="onBackgroundToggle($event.checked)">
|
|
479
|
-
Background
|
|
480
|
-
</mat-slide-toggle>
|
|
481
|
-
<span class="toggle-description"
|
|
482
|
-
>Adds a background behind the text</span
|
|
483
|
-
>
|
|
484
|
-
</div>
|
|
485
|
-
</mat-dialog-content>
|
|
486
|
-
|
|
487
|
-
<mat-dialog-actions align="end">
|
|
488
|
-
<button mat-button (click)="onCancel()">Cancel</button>
|
|
489
|
-
<button mat-flat-button (click)="save()" [disabled]="!hasChanged()">
|
|
490
|
-
Save
|
|
491
|
-
</button>
|
|
492
|
-
</mat-dialog-actions>
|
|
493
|
-
`, 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}\n"] }]
|
|
494
|
-
}] });
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* Directive that automatically adjusts font size to fit text within its parent container.
|
|
498
|
-
* Uses canvas-based measurement for performance and DOM verification for accuracy.
|
|
499
|
-
*
|
|
500
|
-
* @example
|
|
501
|
-
* <div class="container">
|
|
502
|
-
* <span responsiveText [min]="12" [max]="72">Dynamic text here</span>
|
|
503
|
-
* </div>
|
|
504
|
-
*/
|
|
505
|
-
class ResponsiveTextDirective {
|
|
506
|
-
/* ───────────────────────── Inputs with transforms ─────────────── */
|
|
507
|
-
/** Minimum font-size in pixels (accessibility floor) */
|
|
508
|
-
min = input(8, { transform: numberAttribute });
|
|
509
|
-
/** Maximum font-size in pixels (layout ceiling) */
|
|
510
|
-
max = input(512, { transform: numberAttribute });
|
|
511
|
-
/**
|
|
512
|
-
* Line-height: pass a multiplier (e.g. 1.1) or absolute px value.
|
|
513
|
-
* For single-line text a multiplier < 10 is treated as unitless.
|
|
514
|
-
*/
|
|
515
|
-
lineHeight = input(1.1, { transform: numberAttribute });
|
|
516
|
-
/** Whether to observe text mutations after first render */
|
|
517
|
-
observeMutations = input(true, { transform: booleanAttribute });
|
|
518
|
-
/** Debounce delay in ms for resize/mutation callbacks */
|
|
519
|
-
debounceMs = input(16, { transform: numberAttribute });
|
|
520
|
-
/* ───────────────────────── Private state ───────────────────────── */
|
|
521
|
-
el = inject(ElementRef);
|
|
522
|
-
zone = inject(NgZone);
|
|
523
|
-
platformId = inject(PLATFORM_ID);
|
|
524
|
-
destroyRef = inject(DestroyRef);
|
|
525
|
-
// Canvas context - lazy initialization
|
|
526
|
-
_ctx;
|
|
527
|
-
get ctx() {
|
|
528
|
-
if (!this._ctx) {
|
|
529
|
-
const canvas = document.createElement('canvas');
|
|
530
|
-
this._ctx = canvas.getContext('2d', {
|
|
531
|
-
willReadFrequently: true,
|
|
532
|
-
alpha: false,
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
return this._ctx;
|
|
536
|
-
}
|
|
537
|
-
ro;
|
|
538
|
-
mo;
|
|
539
|
-
fitTimeout;
|
|
540
|
-
// Cache for performance
|
|
541
|
-
lastText = '';
|
|
542
|
-
lastMaxW = 0;
|
|
543
|
-
lastMaxH = 0;
|
|
544
|
-
lastFontSize = 0;
|
|
545
|
-
/* ───────────────────────── Lifecycle ──────────────────────────── */
|
|
546
|
-
ngAfterViewInit() {
|
|
547
|
-
if (!isPlatformBrowser(this.platformId))
|
|
548
|
-
return;
|
|
549
|
-
// Set initial styles
|
|
550
|
-
const span = this.el.nativeElement;
|
|
551
|
-
span.style.transition = 'font-size 0.1s ease-out';
|
|
552
|
-
// All observer callbacks run outside Angular's zone
|
|
553
|
-
this.zone.runOutsideAngular(() => {
|
|
554
|
-
this.fit();
|
|
555
|
-
this.observeResize();
|
|
556
|
-
if (this.observeMutations()) {
|
|
557
|
-
this.observeText();
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
ngOnDestroy() {
|
|
562
|
-
this.cleanup();
|
|
563
|
-
}
|
|
564
|
-
/* ───────────────────── Core fitting logic ───────────────────── */
|
|
565
|
-
/**
|
|
566
|
-
* Debounced fit handler to prevent excessive recalculations
|
|
567
|
-
*/
|
|
568
|
-
requestFit = () => {
|
|
569
|
-
if (this.fitTimeout) {
|
|
570
|
-
cancelAnimationFrame(this.fitTimeout);
|
|
571
|
-
}
|
|
572
|
-
this.fitTimeout = requestAnimationFrame(() => {
|
|
573
|
-
this.fit();
|
|
574
|
-
});
|
|
575
|
-
};
|
|
576
|
-
/**
|
|
577
|
-
* Recalculate & apply the ideal font-size
|
|
578
|
-
*/
|
|
579
|
-
fit = () => {
|
|
580
|
-
const span = this.el.nativeElement;
|
|
581
|
-
const parent = span.parentElement;
|
|
582
|
-
if (!parent)
|
|
583
|
-
return;
|
|
584
|
-
const text = span.textContent?.trim() || '';
|
|
585
|
-
if (!text) {
|
|
586
|
-
span.style.fontSize = `${this.min()}px`;
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
// Get available space
|
|
590
|
-
const { maxW, maxH } = this.getAvailableSpace(parent);
|
|
591
|
-
// Check cache to avoid redundant calculations
|
|
592
|
-
if (text === this.lastText &&
|
|
593
|
-
maxW === this.lastMaxW &&
|
|
594
|
-
maxH === this.lastMaxH &&
|
|
595
|
-
this.lastFontSize > 0) {
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
// Calculate ideal font size
|
|
599
|
-
const ideal = this.calcFit(text, maxW, maxH);
|
|
600
|
-
span.style.fontSize = `${ideal}px`;
|
|
601
|
-
// DOM verification pass
|
|
602
|
-
this.verifyFit(span, maxW, maxH, ideal);
|
|
603
|
-
// Update cache
|
|
604
|
-
this.lastText = text;
|
|
605
|
-
this.lastMaxW = maxW;
|
|
606
|
-
this.lastMaxH = maxH;
|
|
607
|
-
this.lastFontSize = parseFloat(span.style.fontSize);
|
|
608
|
-
};
|
|
609
|
-
/**
|
|
610
|
-
* Calculate available space accounting for padding and borders
|
|
611
|
-
*/
|
|
612
|
-
getAvailableSpace(parent) {
|
|
613
|
-
const cs = getComputedStyle(parent);
|
|
614
|
-
const maxW = parent.clientWidth -
|
|
615
|
-
parseFloat(cs.paddingLeft) -
|
|
616
|
-
parseFloat(cs.paddingRight);
|
|
617
|
-
const maxH = parent.clientHeight -
|
|
618
|
-
parseFloat(cs.paddingTop) -
|
|
619
|
-
parseFloat(cs.paddingBottom);
|
|
620
|
-
return { maxW: Math.max(0, maxW), maxH: Math.max(0, maxH) };
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* DOM-based verification to handle sub-pixel discrepancies
|
|
624
|
-
*/
|
|
625
|
-
verifyFit(span, maxW, maxH, ideal) {
|
|
626
|
-
// Use requestAnimationFrame to ensure layout is complete
|
|
627
|
-
requestAnimationFrame(() => {
|
|
628
|
-
if (span.scrollWidth > maxW || span.scrollHeight > maxH) {
|
|
629
|
-
let safe = ideal;
|
|
630
|
-
while (safe > this.min() &&
|
|
631
|
-
(span.scrollWidth > maxW || span.scrollHeight > maxH)) {
|
|
632
|
-
safe -= 0.5; // Finer adjustments
|
|
633
|
-
span.style.fontSize = `${safe}px`;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
/* ───────────────────── Binary search algorithm ────────────────── */
|
|
639
|
-
/**
|
|
640
|
-
* Binary search for optimal font size using canvas measurements
|
|
641
|
-
*/
|
|
642
|
-
calcFit(text, maxW, maxH, precision = 0.1) {
|
|
643
|
-
if (maxW <= 0 || maxH <= 0)
|
|
644
|
-
return this.min();
|
|
645
|
-
const computedStyle = getComputedStyle(this.el.nativeElement);
|
|
646
|
-
const fontFamily = computedStyle.fontFamily || 'sans-serif';
|
|
647
|
-
const fontWeight = computedStyle.fontWeight || '400';
|
|
648
|
-
let lo = this.min();
|
|
649
|
-
let hi = this.max();
|
|
650
|
-
let bestFit = this.min();
|
|
651
|
-
while (hi - lo > precision) {
|
|
652
|
-
const mid = (hi + lo) / 2;
|
|
653
|
-
this.ctx.font = `${fontWeight} ${mid}px ${fontFamily}`;
|
|
654
|
-
const metrics = this.ctx.measureText(text);
|
|
655
|
-
const width = metrics.width;
|
|
656
|
-
// Calculate height based on available metrics
|
|
657
|
-
const height = this.calculateTextHeight(metrics, mid);
|
|
658
|
-
if (width <= maxW && height <= maxH) {
|
|
659
|
-
bestFit = mid;
|
|
660
|
-
lo = mid;
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
hi = mid;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
return Math.floor(bestFit * 100) / 100;
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* Calculate text height from metrics
|
|
670
|
-
*/
|
|
671
|
-
calculateTextHeight(metrics, fontSize) {
|
|
672
|
-
// Use font bounding box metrics if available
|
|
673
|
-
if (metrics.fontBoundingBoxAscent && metrics.fontBoundingBoxDescent) {
|
|
674
|
-
return metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
|
|
675
|
-
}
|
|
676
|
-
// Fallback to actual bounding box
|
|
677
|
-
if (metrics.actualBoundingBoxAscent && metrics.actualBoundingBoxDescent) {
|
|
678
|
-
return metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
679
|
-
}
|
|
680
|
-
// Final fallback using line height
|
|
681
|
-
return this.lineHeight() < 10
|
|
682
|
-
? fontSize * this.lineHeight()
|
|
683
|
-
: this.lineHeight();
|
|
684
|
-
}
|
|
685
|
-
/* ───────────────────────── Observers ─────────────────────────── */
|
|
686
|
-
/**
|
|
687
|
-
* Observe parent container resizes
|
|
688
|
-
*/
|
|
689
|
-
observeResize() {
|
|
690
|
-
if (!('ResizeObserver' in window))
|
|
691
|
-
return;
|
|
692
|
-
this.ro = new ResizeObserver((entries) => {
|
|
693
|
-
// Only trigger if size actually changed
|
|
694
|
-
const entry = entries[0];
|
|
695
|
-
if (entry?.contentRect) {
|
|
696
|
-
this.requestFit();
|
|
697
|
-
}
|
|
698
|
-
});
|
|
699
|
-
const parent = this.el.nativeElement.parentElement;
|
|
700
|
-
if (parent) {
|
|
701
|
-
this.ro.observe(parent);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* Observe text content changes
|
|
706
|
-
*/
|
|
707
|
-
observeText() {
|
|
708
|
-
if (!('MutationObserver' in window))
|
|
709
|
-
return;
|
|
710
|
-
this.mo = new MutationObserver((mutations) => {
|
|
711
|
-
// Check if text actually changed
|
|
712
|
-
const hasTextChange = mutations.some((m) => m.type === 'characterData' ||
|
|
713
|
-
(m.type === 'childList' &&
|
|
714
|
-
(m.addedNodes.length > 0 || m.removedNodes.length > 0)));
|
|
715
|
-
if (hasTextChange) {
|
|
716
|
-
this.requestFit();
|
|
717
|
-
}
|
|
718
|
-
});
|
|
719
|
-
this.mo.observe(this.el.nativeElement, {
|
|
720
|
-
characterData: true,
|
|
721
|
-
childList: true,
|
|
722
|
-
subtree: true,
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Cleanup resources
|
|
727
|
-
*/
|
|
728
|
-
cleanup() {
|
|
729
|
-
this.ro?.disconnect();
|
|
730
|
-
this.mo?.disconnect();
|
|
731
|
-
if (this.fitTimeout) {
|
|
732
|
-
cancelAnimationFrame(this.fitTimeout);
|
|
733
|
-
}
|
|
734
|
-
// Clear canvas context
|
|
735
|
-
this._ctx = undefined;
|
|
736
|
-
}
|
|
737
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ResponsiveTextDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
738
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.0.6", type: ResponsiveTextDirective, isStandalone: true, selector: "[responsiveText]", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", 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": "\"hidden\"", "style.text-overflow": "\"ellipsis\"" } }, ngImport: i0 });
|
|
739
|
-
}
|
|
740
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ResponsiveTextDirective, decorators: [{
|
|
741
|
-
type: Directive,
|
|
742
|
-
args: [{
|
|
743
|
-
selector: '[responsiveText]',
|
|
744
|
-
standalone: true,
|
|
745
|
-
host: {
|
|
746
|
-
'[style.display]': '"block"',
|
|
747
|
-
'[style.width]': '"100%"',
|
|
748
|
-
'[style.white-space]': '"nowrap"',
|
|
749
|
-
'[style.overflow]': '"hidden"',
|
|
750
|
-
'[style.text-overflow]': '"ellipsis"',
|
|
751
|
-
},
|
|
752
|
-
}]
|
|
753
|
-
}] });
|
|
754
|
-
|
|
755
|
-
// label-widget.component.ts
|
|
756
|
-
class LabelWidgetComponent {
|
|
757
|
-
static metadata = {
|
|
758
|
-
widgetTypeid: '@default/label-widget',
|
|
759
|
-
name: 'Label',
|
|
760
|
-
description: 'A generic text label',
|
|
761
|
-
svgIcon: svgIcon$1,
|
|
762
|
-
};
|
|
763
|
-
#sanitizer = inject(DomSanitizer);
|
|
764
|
-
#dialog = inject(MatDialog);
|
|
765
|
-
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon$1);
|
|
766
|
-
state = signal({
|
|
767
|
-
label: '',
|
|
768
|
-
fontSize: 16,
|
|
769
|
-
alignment: 'center',
|
|
770
|
-
fontWeight: 'normal',
|
|
771
|
-
opacity: 1,
|
|
772
|
-
hasBackground: true,
|
|
773
|
-
responsive: false,
|
|
774
|
-
});
|
|
775
|
-
dashboardSetState(state) {
|
|
776
|
-
if (state) {
|
|
777
|
-
this.state.update((current) => ({
|
|
778
|
-
...current,
|
|
779
|
-
...state,
|
|
780
|
-
}));
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
dashboardGetState() {
|
|
784
|
-
return { ...this.state() };
|
|
785
|
-
}
|
|
786
|
-
dashboardEditState() {
|
|
787
|
-
const dialogRef = this.#dialog.open(LabelStateDialogComponent, {
|
|
788
|
-
data: this.dashboardGetState(),
|
|
789
|
-
width: '400px',
|
|
790
|
-
maxWidth: '90vw',
|
|
791
|
-
disableClose: false,
|
|
792
|
-
autoFocus: false,
|
|
793
|
-
});
|
|
794
|
-
dialogRef
|
|
795
|
-
.afterClosed()
|
|
796
|
-
.subscribe((result) => {
|
|
797
|
-
if (result) {
|
|
798
|
-
this.state.set(result);
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
get hasContent() {
|
|
803
|
-
return !!this.state().label?.trim();
|
|
804
|
-
}
|
|
805
|
-
get label() {
|
|
806
|
-
return this.state().label?.trim();
|
|
807
|
-
}
|
|
808
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: LabelWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
809
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", 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\" responsiveText>{{ 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;transform-origin:center center}.svg-placeholder ::ng-deep svg{width:100%;height:100%;display:block;fill:var(--mat-sys-on-surface-variant, #6c757d);transition:fill .2s ease}.has-background .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-on-surface, #1f1f1f)}.svg-wrapper:hover .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-primary, #6750a4)}\n"], dependencies: [{ kind: "directive", type: ResponsiveTextDirective, selector: "[responsiveText]", inputs: ["min", "max", "lineHeight", "observeMutations", "debounceMs"] }] });
|
|
810
|
-
}
|
|
811
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: LabelWidgetComponent, decorators: [{
|
|
812
|
-
type: Component,
|
|
813
|
-
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\" responsiveText>{{ 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;transform-origin:center center}.svg-placeholder ::ng-deep svg{width:100%;height:100%;display:block;fill:var(--mat-sys-on-surface-variant, #6c757d);transition:fill .2s ease}.has-background .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-on-surface, #1f1f1f)}.svg-wrapper:hover .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-primary, #6750a4)}\n"] }]
|
|
814
|
-
}] });
|
|
815
|
-
|
|
816
|
-
// clock-widget.metadata.ts
|
|
817
|
-
const svgIcon = `
|
|
818
|
-
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 800 800">
|
|
819
|
-
<circle cx="400" cy="400" r="400" fill="#fff"/>
|
|
820
|
-
<use transform="matrix(-1,0,0,1,800,0)" xlink:href="#one-half"/>
|
|
821
|
-
<g id="one-half">
|
|
822
|
-
<g id="one-fourth">
|
|
823
|
-
<path d="m400 40v107" stroke="#000" stroke-width="26.7"/>
|
|
824
|
-
<g id="one-twelfth">
|
|
825
|
-
<path d="m580 88.233-42.5 73.612" stroke="#000" stroke-width="26.7"/>
|
|
826
|
-
<g id="one-thirtieth">
|
|
827
|
-
<path id="one-sixtieth" d="m437.63 41.974-3.6585 34.808" stroke="#000" stroke-width="13.6" />
|
|
828
|
-
<use transform="rotate(6 400 400)" xlink:href="#one-sixtieth"/>
|
|
829
|
-
</g>
|
|
830
|
-
<use transform="rotate(12 400 400)" xlink:href="#one-thirtieth"/>
|
|
831
|
-
</g>
|
|
832
|
-
<use transform="rotate(30 400 400)" xlink:href="#one-twelfth"/>
|
|
833
|
-
<use transform="rotate(60 400 400)" xlink:href="#one-twelfth"/>
|
|
834
|
-
</g>
|
|
835
|
-
<use transform="rotate(90 400 400)" xlink:href="#one-fourth"/>
|
|
836
|
-
</g>
|
|
837
|
-
<path class="clock-hour-hand" id="anim-clock-hour-hand" d="m 381.925,476 h 36.15 l 5e-4,-300.03008 L 400,156.25 381.9245,175.96992 Z" transform="rotate(110.2650694444, 400, 400)" />
|
|
838
|
-
<path class="clock-minute-hand" id="anim-clock-minute-hand" d="M 412.063,496.87456 H 387.937 L 385.249,65.68306 400,52.75 414.751,65.68306 Z" transform="rotate(243.1808333333, 400, 400)" />
|
|
839
|
-
</svg>
|
|
840
|
-
`;
|
|
841
|
-
|
|
842
|
-
class ClockStateDialogComponent {
|
|
843
|
-
data = inject(MAT_DIALOG_DATA);
|
|
844
|
-
dialogRef = inject((MatDialogRef));
|
|
845
|
-
// State signals
|
|
846
|
-
mode = signal(this.data.mode ?? 'digital');
|
|
847
|
-
hasBackground = signal(this.data.hasBackground ?? true);
|
|
848
|
-
timeFormat = signal(this.data.timeFormat ?? '24h');
|
|
849
|
-
showSeconds = signal(this.data.showSeconds ?? true);
|
|
850
|
-
// Store original values for comparison
|
|
851
|
-
originalMode = this.data.mode ?? 'digital';
|
|
852
|
-
originalHasBackground = this.data.hasBackground ?? true;
|
|
853
|
-
originalTimeFormat = this.data.timeFormat ?? '24h';
|
|
854
|
-
originalShowSeconds = this.data.showSeconds ?? true;
|
|
855
|
-
// Computed values
|
|
856
|
-
hasChanged = computed(() => this.mode() !== this.originalMode ||
|
|
857
|
-
this.hasBackground() !== this.originalHasBackground ||
|
|
858
|
-
this.timeFormat() !== this.originalTimeFormat ||
|
|
859
|
-
this.showSeconds() !== this.originalShowSeconds);
|
|
860
|
-
onCancel() {
|
|
861
|
-
this.dialogRef.close();
|
|
862
|
-
}
|
|
863
|
-
save() {
|
|
864
|
-
this.dialogRef.close({
|
|
865
|
-
mode: this.mode(),
|
|
866
|
-
hasBackground: this.hasBackground(),
|
|
867
|
-
timeFormat: this.timeFormat(),
|
|
868
|
-
showSeconds: this.showSeconds(),
|
|
869
|
-
});
|
|
870
|
-
}
|
|
871
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ClockStateDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
872
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: ClockStateDialogComponent, isStandalone: true, selector: "demo-clock-state-dialog", ngImport: i0, template: `
|
|
873
|
-
<h2 mat-dialog-title>Clock Settings</h2>
|
|
874
|
-
<mat-dialog-content>
|
|
875
|
-
<div class="mode-selection">
|
|
876
|
-
<label class="section-label">Display Mode</label>
|
|
877
|
-
<mat-radio-group
|
|
878
|
-
[value]="mode()"
|
|
879
|
-
(change)="mode.set($any($event.value))"
|
|
880
|
-
>
|
|
881
|
-
<mat-radio-button value="digital">Digital</mat-radio-button>
|
|
882
|
-
<mat-radio-button value="analog">Analog</mat-radio-button>
|
|
883
|
-
</mat-radio-group>
|
|
884
|
-
</div>
|
|
885
|
-
|
|
886
|
-
<!-- Time Format (only for digital mode) -->
|
|
887
|
-
@if (mode() === 'digital') {
|
|
888
|
-
<div class="format-selection">
|
|
889
|
-
<label class="section-label">Time Format</label>
|
|
890
|
-
<mat-radio-group
|
|
891
|
-
[value]="timeFormat()"
|
|
892
|
-
(change)="timeFormat.set($any($event.value))"
|
|
893
|
-
>
|
|
894
|
-
<mat-radio-button value="24h">24 Hour (14:30:45)</mat-radio-button>
|
|
895
|
-
<mat-radio-button value="12h">12 Hour (2:30:45 PM)</mat-radio-button>
|
|
896
|
-
</mat-radio-group>
|
|
897
|
-
</div>
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
<!-- Show Seconds Toggle (for both digital and analog modes) -->
|
|
901
|
-
<div class="toggle-section">
|
|
902
|
-
<mat-slide-toggle
|
|
903
|
-
[checked]="showSeconds()"
|
|
904
|
-
(change)="showSeconds.set($event.checked)">
|
|
905
|
-
Show Seconds
|
|
906
|
-
</mat-slide-toggle>
|
|
907
|
-
<span class="toggle-description">
|
|
908
|
-
@if (mode() === 'digital') {
|
|
909
|
-
Display seconds in the time
|
|
910
|
-
} @else {
|
|
911
|
-
Show the second hand on the clock
|
|
912
|
-
}
|
|
913
|
-
</span>
|
|
914
|
-
</div>
|
|
915
|
-
|
|
916
|
-
<!-- Background Toggle -->
|
|
917
|
-
<div class="toggle-section">
|
|
918
|
-
<mat-slide-toggle
|
|
919
|
-
[checked]="hasBackground()"
|
|
920
|
-
(change)="hasBackground.set($event.checked)">
|
|
921
|
-
Background
|
|
922
|
-
</mat-slide-toggle>
|
|
923
|
-
<span class="toggle-description"
|
|
924
|
-
>Adds a background behind the clock</span
|
|
925
|
-
>
|
|
926
|
-
</div>
|
|
927
|
-
</mat-dialog-content>
|
|
928
|
-
|
|
929
|
-
<mat-dialog-actions align="end">
|
|
930
|
-
<button mat-button (click)="onCancel()">Cancel</button>
|
|
931
|
-
<button mat-flat-button (click)="save()" [disabled]="!hasChanged()">
|
|
932
|
-
Save
|
|
933
|
-
</button>
|
|
934
|
-
</mat-dialog-actions>
|
|
935
|
-
`, 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"] }] });
|
|
936
|
-
}
|
|
937
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ClockStateDialogComponent, decorators: [{
|
|
938
|
-
type: Component,
|
|
939
|
-
args: [{ selector: 'demo-clock-state-dialog', standalone: true, imports: [
|
|
940
|
-
CommonModule,
|
|
941
|
-
FormsModule,
|
|
942
|
-
MatDialogModule,
|
|
943
|
-
MatButtonModule,
|
|
944
|
-
MatRadioModule,
|
|
945
|
-
MatSlideToggleModule,
|
|
946
|
-
], template: `
|
|
947
|
-
<h2 mat-dialog-title>Clock Settings</h2>
|
|
948
|
-
<mat-dialog-content>
|
|
949
|
-
<div class="mode-selection">
|
|
950
|
-
<label class="section-label">Display Mode</label>
|
|
951
|
-
<mat-radio-group
|
|
952
|
-
[value]="mode()"
|
|
953
|
-
(change)="mode.set($any($event.value))"
|
|
954
|
-
>
|
|
955
|
-
<mat-radio-button value="digital">Digital</mat-radio-button>
|
|
956
|
-
<mat-radio-button value="analog">Analog</mat-radio-button>
|
|
957
|
-
</mat-radio-group>
|
|
958
|
-
</div>
|
|
959
|
-
|
|
960
|
-
<!-- Time Format (only for digital mode) -->
|
|
961
|
-
@if (mode() === 'digital') {
|
|
962
|
-
<div class="format-selection">
|
|
963
|
-
<label class="section-label">Time Format</label>
|
|
964
|
-
<mat-radio-group
|
|
965
|
-
[value]="timeFormat()"
|
|
966
|
-
(change)="timeFormat.set($any($event.value))"
|
|
967
|
-
>
|
|
968
|
-
<mat-radio-button value="24h">24 Hour (14:30:45)</mat-radio-button>
|
|
969
|
-
<mat-radio-button value="12h">12 Hour (2:30:45 PM)</mat-radio-button>
|
|
970
|
-
</mat-radio-group>
|
|
971
|
-
</div>
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
<!-- Show Seconds Toggle (for both digital and analog modes) -->
|
|
975
|
-
<div class="toggle-section">
|
|
976
|
-
<mat-slide-toggle
|
|
977
|
-
[checked]="showSeconds()"
|
|
978
|
-
(change)="showSeconds.set($event.checked)">
|
|
979
|
-
Show Seconds
|
|
980
|
-
</mat-slide-toggle>
|
|
981
|
-
<span class="toggle-description">
|
|
982
|
-
@if (mode() === 'digital') {
|
|
983
|
-
Display seconds in the time
|
|
984
|
-
} @else {
|
|
985
|
-
Show the second hand on the clock
|
|
986
|
-
}
|
|
987
|
-
</span>
|
|
988
|
-
</div>
|
|
989
|
-
|
|
990
|
-
<!-- Background Toggle -->
|
|
991
|
-
<div class="toggle-section">
|
|
992
|
-
<mat-slide-toggle
|
|
993
|
-
[checked]="hasBackground()"
|
|
994
|
-
(change)="hasBackground.set($event.checked)">
|
|
995
|
-
Background
|
|
996
|
-
</mat-slide-toggle>
|
|
997
|
-
<span class="toggle-description"
|
|
998
|
-
>Adds a background behind the clock</span
|
|
999
|
-
>
|
|
1000
|
-
</div>
|
|
1001
|
-
</mat-dialog-content>
|
|
1002
|
-
|
|
1003
|
-
<mat-dialog-actions align="end">
|
|
1004
|
-
<button mat-button (click)="onCancel()">Cancel</button>
|
|
1005
|
-
<button mat-flat-button (click)="save()" [disabled]="!hasChanged()">
|
|
1006
|
-
Save
|
|
1007
|
-
</button>
|
|
1008
|
-
</mat-dialog-actions>
|
|
1009
|
-
`, 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"] }]
|
|
1010
|
-
}] });
|
|
1011
|
-
|
|
1012
|
-
class DigitalClockComponent {
|
|
1013
|
-
#destroyRef = inject(DestroyRef);
|
|
1014
|
-
// Inputs
|
|
1015
|
-
timeFormat = input('24h');
|
|
1016
|
-
showSeconds = input(true);
|
|
1017
|
-
hasBackground = input(false);
|
|
1018
|
-
// Time tracking
|
|
1019
|
-
currentTime = signal(new Date());
|
|
1020
|
-
formattedTime = computed(() => {
|
|
1021
|
-
const time = this.currentTime();
|
|
1022
|
-
const format = this.timeFormat();
|
|
1023
|
-
const showSecs = this.showSeconds();
|
|
1024
|
-
return this.#formatTime(time, format, showSecs);
|
|
1025
|
-
});
|
|
1026
|
-
#intervalId = null;
|
|
1027
|
-
#formatTime(time, format, showSecs) {
|
|
1028
|
-
let hours = time.getHours();
|
|
1029
|
-
const minutes = time.getMinutes();
|
|
1030
|
-
const seconds = time.getSeconds();
|
|
1031
|
-
// Pad with leading zeros
|
|
1032
|
-
const mm = minutes.toString().padStart(2, '0');
|
|
1033
|
-
const ss = seconds.toString().padStart(2, '0');
|
|
1034
|
-
if (format === '12h') {
|
|
1035
|
-
// 12-hour format with AM/PM
|
|
1036
|
-
const ampm = hours >= 12 ? 'PM' : 'AM';
|
|
1037
|
-
hours = hours % 12;
|
|
1038
|
-
if (hours === 0)
|
|
1039
|
-
hours = 12; // Convert 0 to 12 for 12 AM/PM
|
|
1040
|
-
const hh = hours.toString().padStart(2, '0');
|
|
1041
|
-
if (showSecs) {
|
|
1042
|
-
return `${hh}:${mm}:${ss} ${ampm}`;
|
|
1043
|
-
}
|
|
1044
|
-
else {
|
|
1045
|
-
return `${hh}:${mm} ${ampm}`;
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
else {
|
|
1049
|
-
// 24-hour format
|
|
1050
|
-
const hh = hours.toString().padStart(2, '0');
|
|
1051
|
-
if (showSecs) {
|
|
1052
|
-
return `${hh}:${mm}:${ss}`;
|
|
1053
|
-
}
|
|
1054
|
-
else {
|
|
1055
|
-
return `${hh}:${mm}`;
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
constructor() {
|
|
1060
|
-
// Set up time update timer
|
|
1061
|
-
this.#startTimer();
|
|
1062
|
-
// Clean up timer on component destruction
|
|
1063
|
-
this.#destroyRef.onDestroy(() => {
|
|
1064
|
-
this.#stopTimer();
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
#startTimer() {
|
|
1068
|
-
// Sync to the next second boundary for smooth start
|
|
1069
|
-
const now = new Date();
|
|
1070
|
-
const msUntilNextSecond = 1000 - now.getMilliseconds();
|
|
1071
|
-
setTimeout(() => {
|
|
1072
|
-
this.currentTime.set(new Date());
|
|
1073
|
-
// Start the regular 1-second interval
|
|
1074
|
-
this.#intervalId = window.setInterval(() => {
|
|
1075
|
-
this.currentTime.set(new Date());
|
|
1076
|
-
}, 1000);
|
|
1077
|
-
}, msUntilNextSecond);
|
|
1078
|
-
}
|
|
1079
|
-
#stopTimer() {
|
|
1080
|
-
if (this.#intervalId !== null) {
|
|
1081
|
-
clearInterval(this.#intervalId);
|
|
1082
|
-
this.#intervalId = null;
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: DigitalClockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1086
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.0.6", type: DigitalClockComponent, isStandalone: true, selector: "ngx-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 class=\"digital-time\">{{ formattedTime() }}</div>", 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(12px,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(12px,min(15cqw,50cqh),200px)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1087
|
-
}
|
|
1088
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: DigitalClockComponent, decorators: [{
|
|
1089
|
-
type: Component,
|
|
1090
|
-
args: [{ selector: 'ngx-digital-clock', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
1091
|
-
'[class.has-background]': 'hasBackground()',
|
|
1092
|
-
'[class.show-pm]': 'timeFormat() === "12h"',
|
|
1093
|
-
'[class.show-seconds]': 'showSeconds()',
|
|
1094
|
-
'class': 'clock-widget digital'
|
|
1095
|
-
}, template: "<div class=\"digital-time\">{{ formattedTime() }}</div>", 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(12px,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(12px,min(15cqw,50cqh),200px)}\n"] }]
|
|
1096
|
-
}], ctorParameters: () => [] });
|
|
1097
|
-
|
|
1098
|
-
class AnalogClockComponent {
|
|
1099
|
-
#destroyRef = inject(DestroyRef);
|
|
1100
|
-
#renderer = inject(Renderer2);
|
|
1101
|
-
// Inputs
|
|
1102
|
-
hasBackground = input(false);
|
|
1103
|
-
showSeconds = input(true);
|
|
1104
|
-
// ViewChild references for clock hands
|
|
1105
|
-
hourHand = viewChild('hourHand');
|
|
1106
|
-
minuteHand = viewChild('minuteHand');
|
|
1107
|
-
secondHand = viewChild('secondHand');
|
|
1108
|
-
// Time tracking
|
|
1109
|
-
currentTime = signal(new Date());
|
|
1110
|
-
// Computed rotation signals
|
|
1111
|
-
secondHandRotation = computed(() => {
|
|
1112
|
-
const seconds = this.currentTime().getSeconds();
|
|
1113
|
-
return seconds * 6; // 360° / 60s = 6° per second
|
|
1114
|
-
});
|
|
1115
|
-
minuteHandRotation = computed(() => {
|
|
1116
|
-
const time = this.currentTime();
|
|
1117
|
-
const minutes = time.getMinutes();
|
|
1118
|
-
const seconds = time.getSeconds();
|
|
1119
|
-
return minutes * 6 + seconds / 10; // Smooth minute hand movement
|
|
1120
|
-
});
|
|
1121
|
-
hourHandRotation = computed(() => {
|
|
1122
|
-
const time = this.currentTime();
|
|
1123
|
-
const hours = time.getHours() % 12;
|
|
1124
|
-
const minutes = time.getMinutes();
|
|
1125
|
-
const seconds = time.getSeconds();
|
|
1126
|
-
return hours * 30 + minutes / 2 + seconds / 120; // Smooth hour hand movement
|
|
1127
|
-
});
|
|
1128
|
-
#intervalId = null;
|
|
1129
|
-
constructor() {
|
|
1130
|
-
// Set up time update timer
|
|
1131
|
-
this.#startTimer();
|
|
1132
|
-
// Clean up timer on component destruction
|
|
1133
|
-
this.#destroyRef.onDestroy(() => {
|
|
1134
|
-
this.#stopTimer();
|
|
1135
|
-
});
|
|
1136
|
-
// Update DOM when rotations change
|
|
1137
|
-
effect(() => {
|
|
1138
|
-
this.#updateClockHands();
|
|
1139
|
-
});
|
|
1140
|
-
}
|
|
1141
|
-
#startTimer() {
|
|
1142
|
-
// Sync to the next second boundary for smooth start
|
|
1143
|
-
const now = new Date();
|
|
1144
|
-
const msUntilNextSecond = 1000 - now.getMilliseconds();
|
|
1145
|
-
setTimeout(() => {
|
|
1146
|
-
this.currentTime.set(new Date());
|
|
1147
|
-
// Start the regular 1-second interval
|
|
1148
|
-
this.#intervalId = window.setInterval(() => {
|
|
1149
|
-
this.currentTime.set(new Date());
|
|
1150
|
-
}, 1000);
|
|
1151
|
-
}, msUntilNextSecond);
|
|
1152
|
-
}
|
|
1153
|
-
#stopTimer() {
|
|
1154
|
-
if (this.#intervalId !== null) {
|
|
1155
|
-
clearInterval(this.#intervalId);
|
|
1156
|
-
this.#intervalId = null;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
#updateClockHands() {
|
|
1160
|
-
const hourElement = this.hourHand()?.nativeElement;
|
|
1161
|
-
const minuteElement = this.minuteHand()?.nativeElement;
|
|
1162
|
-
const secondElement = this.secondHand()?.nativeElement;
|
|
1163
|
-
if (hourElement) {
|
|
1164
|
-
this.#renderer.setAttribute(hourElement, 'transform', `rotate(${this.hourHandRotation()}, 400, 400)`);
|
|
1165
|
-
}
|
|
1166
|
-
if (minuteElement) {
|
|
1167
|
-
this.#renderer.setAttribute(minuteElement, 'transform', `rotate(${this.minuteHandRotation()}, 400, 400)`);
|
|
1168
|
-
}
|
|
1169
|
-
if (secondElement && this.showSeconds()) {
|
|
1170
|
-
this.#renderer.setAttribute(secondElement, 'transform', `rotate(${this.secondHandRotation()}, 400, 400)`);
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AnalogClockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1174
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.6", type: AnalogClockComponent, isStandalone: true, selector: "ngx-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 xmlns:xlink=\"http://www.w3.org/1999/xlink\"\r\n viewBox=\"0 0 800 800\"\r\n preserveAspectRatio=\"xMidYMid meet\"\r\n >\r\n <!-- <circle cx=\"400\" cy=\"400\" r=\"400\" fill=\"#fff\"/> -->\r\n <use transform=\"matrix(-1,0,0,1,800,0)\" xlink:href=\"#one-half\" />\r\n <g id=\"one-half\">\r\n <g id=\"one-fourth\">\r\n <path d=\"m400 40v107\" stroke=\"#000\" stroke-width=\"26.7\" />\r\n <g id=\"one-twelfth\">\r\n <path\r\n d=\"m580 88.233-42.5 73.612\"\r\n stroke=\"#000\"\r\n stroke-width=\"26.7\"\r\n />\r\n <g id=\"one-thirtieth\">\r\n <path\r\n id=\"one-sixtieth\"\r\n d=\"m437.63 41.974-3.6585 34.808\"\r\n stroke=\"#000\"\r\n stroke-width=\"13.6\"\r\n />\r\n <use transform=\"rotate(6 400 400)\" xlink:href=\"#one-sixtieth\" />\r\n </g>\r\n <use transform=\"rotate(12 400 400)\" xlink:href=\"#one-thirtieth\" />\r\n </g>\r\n <use transform=\"rotate(30 400 400)\" xlink:href=\"#one-twelfth\" />\r\n <use transform=\"rotate(60 400 400)\" xlink:href=\"#one-twelfth\" />\r\n </g>\r\n <use transform=\"rotate(90 400 400)\" xlink:href=\"#one-fourth\" />\r\n </g>\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 fill=\"#a40000\"\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.clock-widget.analog{container-type:size;container-name:analog-clock}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1175
|
-
}
|
|
1176
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: AnalogClockComponent, decorators: [{
|
|
1177
|
-
type: Component,
|
|
1178
|
-
args: [{ selector: 'ngx-analog-clock', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
1179
|
-
'[class.has-background]': 'hasBackground()',
|
|
1180
|
-
'[class.show-seconds]': 'showSeconds()',
|
|
1181
|
-
'class': 'clock-widget analog'
|
|
1182
|
-
}, 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 xmlns:xlink=\"http://www.w3.org/1999/xlink\"\r\n viewBox=\"0 0 800 800\"\r\n preserveAspectRatio=\"xMidYMid meet\"\r\n >\r\n <!-- <circle cx=\"400\" cy=\"400\" r=\"400\" fill=\"#fff\"/> -->\r\n <use transform=\"matrix(-1,0,0,1,800,0)\" xlink:href=\"#one-half\" />\r\n <g id=\"one-half\">\r\n <g id=\"one-fourth\">\r\n <path d=\"m400 40v107\" stroke=\"#000\" stroke-width=\"26.7\" />\r\n <g id=\"one-twelfth\">\r\n <path\r\n d=\"m580 88.233-42.5 73.612\"\r\n stroke=\"#000\"\r\n stroke-width=\"26.7\"\r\n />\r\n <g id=\"one-thirtieth\">\r\n <path\r\n id=\"one-sixtieth\"\r\n d=\"m437.63 41.974-3.6585 34.808\"\r\n stroke=\"#000\"\r\n stroke-width=\"13.6\"\r\n />\r\n <use transform=\"rotate(6 400 400)\" xlink:href=\"#one-sixtieth\" />\r\n </g>\r\n <use transform=\"rotate(12 400 400)\" xlink:href=\"#one-thirtieth\" />\r\n </g>\r\n <use transform=\"rotate(30 400 400)\" xlink:href=\"#one-twelfth\" />\r\n <use transform=\"rotate(60 400 400)\" xlink:href=\"#one-twelfth\" />\r\n </g>\r\n <use transform=\"rotate(90 400 400)\" xlink:href=\"#one-fourth\" />\r\n </g>\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 fill=\"#a40000\"\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.clock-widget.analog{container-type:size;container-name:analog-clock}\n"] }]
|
|
1183
|
-
}], ctorParameters: () => [] });
|
|
1184
|
-
|
|
1185
|
-
// clock-widget.component.ts
|
|
1186
|
-
class ClockWidgetComponent {
|
|
1187
|
-
static metadata = {
|
|
1188
|
-
widgetTypeid: '@default/clock-widget',
|
|
1189
|
-
name: 'Clock',
|
|
1190
|
-
description: 'Display time in analog or digital format',
|
|
1191
|
-
svgIcon,
|
|
1192
|
-
};
|
|
1193
|
-
#sanitizer = inject(DomSanitizer);
|
|
1194
|
-
#dialog = inject(MatDialog);
|
|
1195
|
-
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon);
|
|
1196
|
-
state = signal({
|
|
1197
|
-
mode: 'analog',
|
|
1198
|
-
hasBackground: true,
|
|
1199
|
-
timeFormat: '24h',
|
|
1200
|
-
showSeconds: true,
|
|
1201
|
-
});
|
|
1202
|
-
constructor() {
|
|
1203
|
-
// No timer logic needed - DigitalClock manages its own time
|
|
1204
|
-
}
|
|
1205
|
-
dashboardSetState(state) {
|
|
1206
|
-
if (state) {
|
|
1207
|
-
this.state.update((current) => ({
|
|
1208
|
-
...current,
|
|
1209
|
-
...state,
|
|
1210
|
-
}));
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
dashboardGetState() {
|
|
1214
|
-
return { ...this.state() };
|
|
1215
|
-
}
|
|
1216
|
-
dashboardEditState() {
|
|
1217
|
-
const dialogRef = this.#dialog.open(ClockStateDialogComponent, {
|
|
1218
|
-
data: this.dashboardGetState(),
|
|
1219
|
-
width: '400px',
|
|
1220
|
-
maxWidth: '90vw',
|
|
1221
|
-
disableClose: false,
|
|
1222
|
-
autoFocus: false,
|
|
1223
|
-
});
|
|
1224
|
-
dialogRef
|
|
1225
|
-
.afterClosed()
|
|
1226
|
-
.subscribe((result) => {
|
|
1227
|
-
if (result) {
|
|
1228
|
-
this.state.set(result);
|
|
1229
|
-
}
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
get isAnalog() {
|
|
1233
|
-
return this.state().mode === 'analog';
|
|
1234
|
-
}
|
|
1235
|
-
get isDigital() {
|
|
1236
|
-
return this.state().mode === 'digital';
|
|
1237
|
-
}
|
|
1238
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ClockWidgetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1239
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: ClockWidgetComponent, isStandalone: true, selector: "ngx-dashboard-clock-widget", ngImport: i0, template: "@if (isDigital) {\r\n <ngx-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 <ngx-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;fill:var(--mat-sys-on-surface-variant, #6c757d);transition:fill .2s ease}.has-background .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-on-surface, #1f1f1f)}.svg-wrapper:hover .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-primary, #6750a4)}\n"], dependencies: [{ kind: "component", type: DigitalClockComponent, selector: "ngx-digital-clock", inputs: ["timeFormat", "showSeconds", "hasBackground"] }, { kind: "component", type: AnalogClockComponent, selector: "ngx-analog-clock", inputs: ["hasBackground", "showSeconds"] }] });
|
|
1240
|
-
}
|
|
1241
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ClockWidgetComponent, decorators: [{
|
|
1242
|
-
type: Component,
|
|
1243
|
-
args: [{ selector: 'ngx-dashboard-clock-widget', standalone: true, imports: [DigitalClockComponent, AnalogClockComponent], template: "@if (isDigital) {\r\n <ngx-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 <ngx-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;fill:var(--mat-sys-on-surface-variant, #6c757d);transition:fill .2s ease}.has-background .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-on-surface, #1f1f1f)}.svg-wrapper:hover .svg-placeholder ::ng-deep svg{fill:var(--mat-sys-primary, #6750a4)}\n"] }]
|
|
1244
|
-
}], ctorParameters: () => [] });
|
|
1245
|
-
|
|
1246
|
-
/*
|
|
1247
|
-
* Public API Surface of ngx-dashboard-widgets
|
|
1248
|
-
*/
|
|
1249
|
-
|
|
1250
|
-
/**
|
|
1251
|
-
* Generated bundle index. Do not edit.
|
|
1252
|
-
*/
|
|
1253
|
-
|
|
1254
|
-
export { ArrowWidgetComponent, ClockWidgetComponent, LabelWidgetComponent, ResponsiveTextDirective };
|
|
1255
|
-
//# sourceMappingURL=dragonworks-ngx-dashboard-widgets.mjs.map
|