@dragonworks/ngx-dashboard-widgets 20.0.6 → 20.1.1
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/fesm2022/dragonworks-ngx-dashboard-widgets.mjs +2251 -0
- package/fesm2022/dragonworks-ngx-dashboard-widgets.mjs.map +1 -0
- package/index.d.ts +532 -0
- package/package.json +42 -31
- package/ng-package.json +0 -7
- package/src/lib/arrow-widget/arrow-state-dialog.component.ts +0 -187
- package/src/lib/arrow-widget/arrow-widget.component.html +0 -9
- package/src/lib/arrow-widget/arrow-widget.component.scss +0 -52
- package/src/lib/arrow-widget/arrow-widget.component.ts +0 -78
- package/src/lib/arrow-widget/arrow-widget.metadata.ts +0 -3
- package/src/lib/clock-widget/analog-clock/analog-clock.component.html +0 -66
- package/src/lib/clock-widget/analog-clock/analog-clock.component.scss +0 -103
- package/src/lib/clock-widget/analog-clock/analog-clock.component.ts +0 -120
- package/src/lib/clock-widget/clock-state-dialog.component.ts +0 -170
- package/src/lib/clock-widget/clock-widget.component.html +0 -16
- package/src/lib/clock-widget/clock-widget.component.scss +0 -160
- package/src/lib/clock-widget/clock-widget.component.ts +0 -87
- package/src/lib/clock-widget/clock-widget.metadata.ts +0 -42
- package/src/lib/clock-widget/digital-clock/__tests__/digital-clock.component.spec.ts +0 -276
- package/src/lib/clock-widget/digital-clock/digital-clock.component.html +0 -1
- package/src/lib/clock-widget/digital-clock/digital-clock.component.scss +0 -43
- package/src/lib/clock-widget/digital-clock/digital-clock.component.ts +0 -105
- package/src/lib/directives/__tests__/responsive-text.directive.spec.ts +0 -906
- package/src/lib/directives/responsive-text.directive.ts +0 -334
- package/src/lib/label-widget/__tests__/label-widget.component.spec.ts +0 -539
- package/src/lib/label-widget/label-state-dialog.component.ts +0 -385
- package/src/lib/label-widget/label-widget.component.html +0 -21
- package/src/lib/label-widget/label-widget.component.scss +0 -112
- package/src/lib/label-widget/label-widget.component.ts +0 -96
- package/src/lib/label-widget/label-widget.metadata.ts +0 -3
- package/src/public-api.ts +0 -7
- package/tsconfig.lib.json +0 -15
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -14
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
import { Component, inject, signal, computed } from '@angular/core';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import { FormsModule } from '@angular/forms';
|
|
4
|
-
import {
|
|
5
|
-
MAT_DIALOG_DATA,
|
|
6
|
-
MatDialogRef,
|
|
7
|
-
MatDialogModule,
|
|
8
|
-
} from '@angular/material/dialog';
|
|
9
|
-
import { MatButtonModule } from '@angular/material/button';
|
|
10
|
-
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
11
|
-
import { MatInputModule } from '@angular/material/input';
|
|
12
|
-
import { MatSelectModule } from '@angular/material/select';
|
|
13
|
-
import { MatSliderModule } from '@angular/material/slider';
|
|
14
|
-
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; // Add this import
|
|
15
|
-
import { LabelWidgetState } from './label-widget.component';
|
|
16
|
-
|
|
17
|
-
@Component({
|
|
18
|
-
selector: 'lib-label-state-dialog',
|
|
19
|
-
standalone: true,
|
|
20
|
-
imports: [
|
|
21
|
-
CommonModule,
|
|
22
|
-
FormsModule,
|
|
23
|
-
MatDialogModule,
|
|
24
|
-
MatButtonModule,
|
|
25
|
-
MatFormFieldModule,
|
|
26
|
-
MatInputModule,
|
|
27
|
-
MatSelectModule,
|
|
28
|
-
MatSliderModule,
|
|
29
|
-
MatSlideToggleModule, // Add this import
|
|
30
|
-
],
|
|
31
|
-
template: `
|
|
32
|
-
<h2 mat-dialog-title>Label Settings</h2>
|
|
33
|
-
<mat-dialog-content>
|
|
34
|
-
<mat-form-field appearance="outline" class="label-text-field">
|
|
35
|
-
<mat-label>Label Text</mat-label>
|
|
36
|
-
<input
|
|
37
|
-
matInput
|
|
38
|
-
type="text"
|
|
39
|
-
[value]="label()"
|
|
40
|
-
(input)="label.set($any($event.target).value)"
|
|
41
|
-
placeholder="Enter your label text..."
|
|
42
|
-
/>
|
|
43
|
-
</mat-form-field>
|
|
44
|
-
|
|
45
|
-
<!-- Responsive Text Toggle -->
|
|
46
|
-
<div class="toggle-section">
|
|
47
|
-
<mat-slide-toggle
|
|
48
|
-
[checked]="responsive()"
|
|
49
|
-
(change)="responsive.set($event.checked)">
|
|
50
|
-
Responsive Text
|
|
51
|
-
</mat-slide-toggle>
|
|
52
|
-
<span class="toggle-description"
|
|
53
|
-
>Automatically adjust text size to fit the widget</span
|
|
54
|
-
>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<!-- Responsive Font Size Constraints (only shown when responsive is enabled) -->
|
|
58
|
-
@if (responsive()) {
|
|
59
|
-
<div class="responsive-section">
|
|
60
|
-
<div class="section-label">Font Size Limits</div>
|
|
61
|
-
<div class="row-layout">
|
|
62
|
-
<mat-form-field appearance="outline">
|
|
63
|
-
<mat-label>Min Size (px)</mat-label>
|
|
64
|
-
<input
|
|
65
|
-
matInput
|
|
66
|
-
type="number"
|
|
67
|
-
[value]="minFontSize()"
|
|
68
|
-
(input)="validateAndCorrectMinFontSize(+$any($event.target).value)"
|
|
69
|
-
(blur)="validateAndCorrectMinFontSize(minFontSize())"
|
|
70
|
-
min="8"
|
|
71
|
-
max="24"
|
|
72
|
-
placeholder="8"
|
|
73
|
-
/>
|
|
74
|
-
@if (!isMinFontSizeValid() || !isFontSizeRangeValid()) {
|
|
75
|
-
<mat-error>
|
|
76
|
-
@if (!isMinFontSizeValid()) {
|
|
77
|
-
Must be between 8-24px
|
|
78
|
-
} @else {
|
|
79
|
-
Must be less than max size
|
|
80
|
-
}
|
|
81
|
-
</mat-error>
|
|
82
|
-
} @else {
|
|
83
|
-
<mat-hint>8-24px range</mat-hint>
|
|
84
|
-
}
|
|
85
|
-
</mat-form-field>
|
|
86
|
-
|
|
87
|
-
<mat-form-field appearance="outline">
|
|
88
|
-
<mat-label>Max Size (px)</mat-label>
|
|
89
|
-
<input
|
|
90
|
-
matInput
|
|
91
|
-
type="number"
|
|
92
|
-
[value]="maxFontSize()"
|
|
93
|
-
(input)="validateAndCorrectMaxFontSize(+$any($event.target).value)"
|
|
94
|
-
(blur)="validateAndCorrectMaxFontSize(maxFontSize())"
|
|
95
|
-
min="16"
|
|
96
|
-
max="128"
|
|
97
|
-
placeholder="64"
|
|
98
|
-
/>
|
|
99
|
-
@if (!isMaxFontSizeValid() || !isFontSizeRangeValid()) {
|
|
100
|
-
<mat-error>
|
|
101
|
-
@if (!isMaxFontSizeValid()) {
|
|
102
|
-
Must be between 16-128px
|
|
103
|
-
} @else {
|
|
104
|
-
Must be greater than min size
|
|
105
|
-
}
|
|
106
|
-
</mat-error>
|
|
107
|
-
} @else {
|
|
108
|
-
<mat-hint>16-128px range</mat-hint>
|
|
109
|
-
}
|
|
110
|
-
</mat-form-field>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
<div class="row-layout">
|
|
116
|
-
<mat-form-field appearance="outline">
|
|
117
|
-
<mat-label>Font Size (px)</mat-label>
|
|
118
|
-
<input
|
|
119
|
-
matInput
|
|
120
|
-
type="number"
|
|
121
|
-
[value]="fontSize()"
|
|
122
|
-
(input)="fontSize.set(+$any($event.target).value)"
|
|
123
|
-
[disabled]="responsive()"
|
|
124
|
-
min="8"
|
|
125
|
-
max="48"
|
|
126
|
-
placeholder="16"
|
|
127
|
-
/>
|
|
128
|
-
</mat-form-field>
|
|
129
|
-
|
|
130
|
-
<mat-form-field appearance="outline">
|
|
131
|
-
<mat-label>Alignment</mat-label>
|
|
132
|
-
<mat-select
|
|
133
|
-
[value]="alignment()"
|
|
134
|
-
(selectionChange)="alignment.set($any($event.value))"
|
|
135
|
-
>
|
|
136
|
-
<mat-option value="left">Left</mat-option>
|
|
137
|
-
<mat-option value="center">Center</mat-option>
|
|
138
|
-
<mat-option value="right">Right</mat-option>
|
|
139
|
-
</mat-select>
|
|
140
|
-
</mat-form-field>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
<mat-form-field appearance="outline">
|
|
144
|
-
<mat-label>Font Weight</mat-label>
|
|
145
|
-
<mat-select
|
|
146
|
-
[value]="fontWeight()"
|
|
147
|
-
(selectionChange)="fontWeight.set($any($event.value))"
|
|
148
|
-
>
|
|
149
|
-
<mat-option value="normal">Normal</mat-option>
|
|
150
|
-
<mat-option value="bold">Bold</mat-option>
|
|
151
|
-
</mat-select>
|
|
152
|
-
</mat-form-field>
|
|
153
|
-
|
|
154
|
-
<!-- Opacity Slider -->
|
|
155
|
-
<div class="slider-section">
|
|
156
|
-
<div class="slider-label">Opacity: {{ formatOpacity(opacity()) }}%</div>
|
|
157
|
-
<mat-slider [min]="0.1" [max]="1" [step]="0.1">
|
|
158
|
-
<input matSliderThumb [(ngModel)]="opacity" />
|
|
159
|
-
</mat-slider>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<!-- Background Toggle -->
|
|
163
|
-
<div class="toggle-section">
|
|
164
|
-
<mat-slide-toggle
|
|
165
|
-
[checked]="!transparentBackground()"
|
|
166
|
-
(change)="onBackgroundToggle($event.checked)">
|
|
167
|
-
Background
|
|
168
|
-
</mat-slide-toggle>
|
|
169
|
-
<span class="toggle-description"
|
|
170
|
-
>Adds a background behind the text</span
|
|
171
|
-
>
|
|
172
|
-
</div>
|
|
173
|
-
</mat-dialog-content>
|
|
174
|
-
|
|
175
|
-
<mat-dialog-actions align="end">
|
|
176
|
-
<button mat-button (click)="onCancel()">Cancel</button>
|
|
177
|
-
<button mat-flat-button (click)="save()" [disabled]="!hasChanged() || !isFormValid()">
|
|
178
|
-
Save
|
|
179
|
-
</button>
|
|
180
|
-
</mat-dialog-actions>
|
|
181
|
-
`,
|
|
182
|
-
styles: [
|
|
183
|
-
`
|
|
184
|
-
mat-dialog-content {
|
|
185
|
-
display: block;
|
|
186
|
-
overflow-y: auto;
|
|
187
|
-
overflow-x: hidden;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
mat-form-field {
|
|
191
|
-
width: 100%;
|
|
192
|
-
display: block;
|
|
193
|
-
margin-bottom: 1rem;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.label-text-field {
|
|
197
|
-
margin-top: 1rem;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/* Side-by-side layout for font size and alignment */
|
|
201
|
-
.row-layout {
|
|
202
|
-
display: grid;
|
|
203
|
-
grid-template-columns: 1fr 1fr;
|
|
204
|
-
gap: 1rem;
|
|
205
|
-
margin-bottom: 1rem;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.row-layout mat-form-field {
|
|
209
|
-
margin-bottom: 0;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/* Opacity slider section */
|
|
213
|
-
.slider-section {
|
|
214
|
-
margin-bottom: 1.5rem;
|
|
215
|
-
margin-right: 1rem;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.slider-label {
|
|
219
|
-
display: block;
|
|
220
|
-
margin-bottom: 0.5rem;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
mat-slider {
|
|
224
|
-
width: 100%;
|
|
225
|
-
display: block;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/* Toggle section */
|
|
229
|
-
.toggle-section {
|
|
230
|
-
display: flex;
|
|
231
|
-
align-items: center;
|
|
232
|
-
gap: 0.75rem;
|
|
233
|
-
margin-bottom: 1rem;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.toggle-description {
|
|
237
|
-
margin: 0;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/* Responsive font size section */
|
|
241
|
-
.responsive-section {
|
|
242
|
-
margin-bottom: 1.5rem;
|
|
243
|
-
padding: 1rem;
|
|
244
|
-
border-radius: 12px;
|
|
245
|
-
background-color: var(--mat-app-surface-variant, rgba(var(--mat-app-on-surface-rgb, 0, 0, 0), 0.05));
|
|
246
|
-
border: 1px solid var(--mat-app-outline-variant, rgba(var(--mat-app-on-surface-rgb, 0, 0, 0), 0.12));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.section-label {
|
|
250
|
-
display: block;
|
|
251
|
-
margin-bottom: 0.75rem;
|
|
252
|
-
font-weight: 500;
|
|
253
|
-
color: var(--mat-app-on-surface-variant, rgba(var(--mat-app-on-surface-rgb, 0, 0, 0), 0.6));
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/* Ensure responsive section row layout has no bottom margin */
|
|
257
|
-
.responsive-section .row-layout {
|
|
258
|
-
margin-bottom: 0;
|
|
259
|
-
}
|
|
260
|
-
`,
|
|
261
|
-
],
|
|
262
|
-
})
|
|
263
|
-
export class LabelStateDialogComponent {
|
|
264
|
-
private readonly data = inject<LabelWidgetState>(MAT_DIALOG_DATA);
|
|
265
|
-
private readonly dialogRef = inject(MatDialogRef<LabelStateDialogComponent>);
|
|
266
|
-
|
|
267
|
-
// State signals
|
|
268
|
-
readonly label = signal<string>(this.data.label ?? '');
|
|
269
|
-
readonly fontSize = signal<number>(this.data.fontSize ?? 16);
|
|
270
|
-
readonly alignment = signal<'left' | 'center' | 'right'>(
|
|
271
|
-
this.data.alignment ?? 'center'
|
|
272
|
-
);
|
|
273
|
-
readonly fontWeight = signal<'normal' | 'bold'>(
|
|
274
|
-
this.data.fontWeight ?? 'normal'
|
|
275
|
-
);
|
|
276
|
-
readonly opacity = signal<number>(this.data.opacity ?? 1);
|
|
277
|
-
readonly hasBackground = signal<boolean>(this.data.hasBackground ?? true);
|
|
278
|
-
readonly transparentBackground = signal<boolean>(!(this.data.hasBackground ?? true));
|
|
279
|
-
readonly responsive = signal<boolean>(this.data.responsive ?? false);
|
|
280
|
-
// Responsive font size constraints
|
|
281
|
-
readonly minFontSize = signal<number>(this.data.minFontSize ?? 8);
|
|
282
|
-
readonly maxFontSize = signal<number>(this.data.maxFontSize ?? 64);
|
|
283
|
-
|
|
284
|
-
// Store original values for comparison
|
|
285
|
-
private readonly originalLabel = this.data.label ?? '';
|
|
286
|
-
private readonly originalFontSize = this.data.fontSize ?? 16;
|
|
287
|
-
private readonly originalAlignment = this.data.alignment ?? 'center';
|
|
288
|
-
private readonly originalFontWeight = this.data.fontWeight ?? 'normal';
|
|
289
|
-
private readonly originalOpacity = this.data.opacity ?? 1;
|
|
290
|
-
private readonly originalHasBackground = this.data.hasBackground ?? true;
|
|
291
|
-
private readonly originalResponsive = this.data.responsive ?? false;
|
|
292
|
-
private readonly originalMinFontSize = this.data.minFontSize ?? 8;
|
|
293
|
-
private readonly originalMaxFontSize = this.data.maxFontSize ?? 64;
|
|
294
|
-
|
|
295
|
-
// Validation computed properties
|
|
296
|
-
readonly isMinFontSizeValid = computed(() => {
|
|
297
|
-
const min = this.minFontSize();
|
|
298
|
-
return min >= 8 && min <= 24;
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
readonly isMaxFontSizeValid = computed(() => {
|
|
302
|
-
const max = this.maxFontSize();
|
|
303
|
-
return max >= 16 && max <= 128;
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
readonly isFontSizeRangeValid = computed(() =>
|
|
307
|
-
this.minFontSize() < this.maxFontSize()
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
readonly isFormValid = computed(() =>
|
|
311
|
-
this.isMinFontSizeValid() &&
|
|
312
|
-
this.isMaxFontSizeValid() &&
|
|
313
|
-
this.isFontSizeRangeValid()
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
// Computed values
|
|
317
|
-
readonly hasChanged = computed(
|
|
318
|
-
() =>
|
|
319
|
-
this.label() !== this.originalLabel ||
|
|
320
|
-
this.fontSize() !== this.originalFontSize ||
|
|
321
|
-
this.alignment() !== this.originalAlignment ||
|
|
322
|
-
this.fontWeight() !== this.originalFontWeight ||
|
|
323
|
-
this.opacity() !== this.originalOpacity ||
|
|
324
|
-
this.hasBackground() !== this.originalHasBackground ||
|
|
325
|
-
this.responsive() !== this.originalResponsive ||
|
|
326
|
-
this.minFontSize() !== this.originalMinFontSize ||
|
|
327
|
-
this.maxFontSize() !== this.originalMaxFontSize
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
formatOpacity(value: number): number {
|
|
331
|
-
return Math.round(value * 100);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
formatOpacitySlider = (value: number): string => {
|
|
335
|
-
return `${Math.round(value * 100)}%`;
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
// Validation methods with robust min < max enforcement
|
|
339
|
-
validateAndCorrectMinFontSize(value: number): void {
|
|
340
|
-
// Clamp to valid range
|
|
341
|
-
const corrected = Math.max(8, Math.min(24, value));
|
|
342
|
-
this.minFontSize.set(corrected);
|
|
343
|
-
|
|
344
|
-
// Ensure min < max with adequate gap
|
|
345
|
-
if (corrected >= this.maxFontSize()) {
|
|
346
|
-
const newMax = Math.min(128, corrected + 8); // Ensure at least 8px gap
|
|
347
|
-
this.maxFontSize.set(newMax);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
validateAndCorrectMaxFontSize(value: number): void {
|
|
352
|
-
// Clamp to valid range
|
|
353
|
-
const corrected = Math.max(16, Math.min(128, value));
|
|
354
|
-
this.maxFontSize.set(corrected);
|
|
355
|
-
|
|
356
|
-
// Ensure min < max with adequate gap
|
|
357
|
-
if (corrected <= this.minFontSize()) {
|
|
358
|
-
const newMin = Math.max(8, corrected - 8); // Ensure at least 8px gap
|
|
359
|
-
this.minFontSize.set(newMin);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
onBackgroundToggle(hasWhiteBackground: boolean): void {
|
|
364
|
-
this.hasBackground.set(hasWhiteBackground);
|
|
365
|
-
this.transparentBackground.set(!hasWhiteBackground);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
onCancel(): void {
|
|
369
|
-
this.dialogRef.close();
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
save(): void {
|
|
373
|
-
this.dialogRef.close({
|
|
374
|
-
label: this.label(),
|
|
375
|
-
fontSize: this.fontSize(),
|
|
376
|
-
alignment: this.alignment(),
|
|
377
|
-
fontWeight: this.fontWeight(),
|
|
378
|
-
opacity: this.opacity(),
|
|
379
|
-
hasBackground: this.hasBackground(),
|
|
380
|
-
responsive: this.responsive(),
|
|
381
|
-
minFontSize: this.minFontSize(),
|
|
382
|
-
maxFontSize: this.maxFontSize(),
|
|
383
|
-
} as LabelWidgetState);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
@if (hasContent) {
|
|
2
|
-
<div
|
|
3
|
-
class="label-widget"
|
|
4
|
-
[style.fontSize.rem]="state().responsive ? null : state().fontSize! / 16"
|
|
5
|
-
[style.--widget-opacity]="state().opacity"
|
|
6
|
-
[class.text-left]="state().alignment === 'left'"
|
|
7
|
-
[class.text-right]="state().alignment === 'right'"
|
|
8
|
-
[class.font-bold]="state().fontWeight === 'bold'"
|
|
9
|
-
[class.has-background]="state().hasBackground"
|
|
10
|
-
>
|
|
11
|
-
@if (state().responsive) {
|
|
12
|
-
<div class="label-text" responsiveText [minFontSize]="minFontSize()" [maxFontSize]="maxFontSize()">{{ label }}</div>
|
|
13
|
-
} @else {
|
|
14
|
-
<div class="label-text">{{ label }}</div>
|
|
15
|
-
}
|
|
16
|
-
</div>
|
|
17
|
-
} @else {
|
|
18
|
-
<div class="svg-wrapper" [class.has-background]="state().hasBackground">
|
|
19
|
-
<div class="svg-placeholder" [innerHTML]="safeSvgIcon"></div>
|
|
20
|
-
</div>
|
|
21
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
// label-widget.component.scss
|
|
2
|
-
:host {
|
|
3
|
-
display: block;
|
|
4
|
-
container-type: size;
|
|
5
|
-
width: 100%;
|
|
6
|
-
height: 100%;
|
|
7
|
-
overflow: hidden;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// Shared styles for both widget types
|
|
11
|
-
%widget-base {
|
|
12
|
-
display: flex;
|
|
13
|
-
align-items: center;
|
|
14
|
-
justify-content: center;
|
|
15
|
-
height: 100%;
|
|
16
|
-
width: 100%;
|
|
17
|
-
box-sizing: border-box;
|
|
18
|
-
transition: background-color var(--mat-sys-motion-duration-medium2)
|
|
19
|
-
var(--mat-sys-motion-easing-standard);
|
|
20
|
-
|
|
21
|
-
&.has-background {
|
|
22
|
-
background-color: var(--mat-sys-surface-container-high);
|
|
23
|
-
border-radius: 4px;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Text widget styles
|
|
28
|
-
.label-widget {
|
|
29
|
-
@extend %widget-base;
|
|
30
|
-
|
|
31
|
-
overflow: hidden;
|
|
32
|
-
container-type: size;
|
|
33
|
-
padding: var(--mat-sys-spacing-4);
|
|
34
|
-
color: var(--mat-sys-on-surface-variant, #6c757d);
|
|
35
|
-
|
|
36
|
-
opacity: var(--widget-opacity, 1);
|
|
37
|
-
|
|
38
|
-
&.text-left {
|
|
39
|
-
justify-content: flex-start;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
&.text-right {
|
|
43
|
-
justify-content: flex-end;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
&.has-background {
|
|
47
|
-
color: var(--mat-sys-on-surface, #1f1f1f);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
&:hover {
|
|
51
|
-
opacity: 0.3;
|
|
52
|
-
color: var(--mat-sys-primary, #6750a4);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.label-text {
|
|
57
|
-
width: 100%;
|
|
58
|
-
text-align: center;
|
|
59
|
-
overflow-wrap: break-word;
|
|
60
|
-
transition: color 0.2s ease;
|
|
61
|
-
|
|
62
|
-
.text-left & {
|
|
63
|
-
text-align: left;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.text-right & {
|
|
67
|
-
text-align: right;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.font-bold & {
|
|
71
|
-
font-weight: bold;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// When using responsive text directive, don't break words
|
|
75
|
-
&[responsiveText] {
|
|
76
|
-
overflow-wrap: normal;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// SVG widget styles
|
|
81
|
-
.svg-wrapper {
|
|
82
|
-
@extend %widget-base;
|
|
83
|
-
overflow: hidden;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.svg-placeholder {
|
|
87
|
-
width: min(80cqw, 80cqh);
|
|
88
|
-
aspect-ratio: 1 / 1;
|
|
89
|
-
opacity: 0.3;
|
|
90
|
-
transition: transform 0.3s ease-in-out, opacity 0.3s ease;
|
|
91
|
-
transform-origin: center center;
|
|
92
|
-
|
|
93
|
-
::ng-deep svg {
|
|
94
|
-
width: 100%;
|
|
95
|
-
height: 100%;
|
|
96
|
-
display: block;
|
|
97
|
-
fill: var(--mat-sys-on-surface-variant, #6c757d);
|
|
98
|
-
transition: fill 0.2s ease;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.has-background & ::ng-deep svg {
|
|
102
|
-
fill: var(--mat-sys-on-surface, #1f1f1f);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.svg-wrapper:hover .svg-placeholder {
|
|
107
|
-
// opacity: 1; // Full opacity on hover for vibrant primary color
|
|
108
|
-
|
|
109
|
-
::ng-deep svg {
|
|
110
|
-
fill: var(--mat-sys-primary, #6750a4);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
// label-widget.component.ts
|
|
2
|
-
import { Component, inject, signal, computed } from '@angular/core';
|
|
3
|
-
import { Widget, WidgetMetadata } from '@dragonworks/ngx-dashboard';
|
|
4
|
-
import { svgIcon } from './label-widget.metadata';
|
|
5
|
-
import { DomSanitizer } from '@angular/platform-browser';
|
|
6
|
-
import { LabelStateDialogComponent } from './label-state-dialog.component';
|
|
7
|
-
import { MatDialog } from '@angular/material/dialog';
|
|
8
|
-
import { ResponsiveTextDirective } from '../directives/responsive-text.directive';
|
|
9
|
-
|
|
10
|
-
export interface LabelWidgetState {
|
|
11
|
-
label: string;
|
|
12
|
-
fontSize?: number;
|
|
13
|
-
alignment?: 'left' | 'center' | 'right';
|
|
14
|
-
fontWeight?: 'normal' | 'bold';
|
|
15
|
-
opacity?: number;
|
|
16
|
-
hasBackground?: boolean;
|
|
17
|
-
responsive?: boolean;
|
|
18
|
-
// Font size constraints for responsive text (px values)
|
|
19
|
-
minFontSize?: number; // Default: 8px (accessible minimum)
|
|
20
|
-
maxFontSize?: number; // Default: 64px (practical widget maximum)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@Component({
|
|
24
|
-
selector: 'ngx-dashboard-label-widget',
|
|
25
|
-
imports: [ResponsiveTextDirective],
|
|
26
|
-
templateUrl: './label-widget.component.html',
|
|
27
|
-
styleUrl: './label-widget.component.scss',
|
|
28
|
-
})
|
|
29
|
-
export class LabelWidgetComponent implements Widget {
|
|
30
|
-
static metadata: WidgetMetadata = {
|
|
31
|
-
widgetTypeid: '@default/label-widget',
|
|
32
|
-
name: 'Label',
|
|
33
|
-
description: 'A generic text label',
|
|
34
|
-
svgIcon,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
readonly #sanitizer = inject(DomSanitizer);
|
|
38
|
-
readonly #dialog = inject(MatDialog);
|
|
39
|
-
|
|
40
|
-
safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon);
|
|
41
|
-
|
|
42
|
-
state = signal<LabelWidgetState>({
|
|
43
|
-
label: '',
|
|
44
|
-
fontSize: 16,
|
|
45
|
-
alignment: 'center',
|
|
46
|
-
fontWeight: 'normal',
|
|
47
|
-
opacity: 1,
|
|
48
|
-
hasBackground: true,
|
|
49
|
-
responsive: false,
|
|
50
|
-
minFontSize: 8, // Accessible minimum for responsive text
|
|
51
|
-
maxFontSize: 64, // Practical maximum for widget display
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
dashboardSetState(state?: unknown) {
|
|
55
|
-
if (state) {
|
|
56
|
-
this.state.update((current) => ({
|
|
57
|
-
...current,
|
|
58
|
-
...(state as LabelWidgetState),
|
|
59
|
-
}));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
dashboardGetState(): LabelWidgetState {
|
|
64
|
-
return { ...this.state() };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
dashboardEditState(): void {
|
|
68
|
-
const dialogRef = this.#dialog.open(LabelStateDialogComponent, {
|
|
69
|
-
data: this.dashboardGetState(),
|
|
70
|
-
width: '400px',
|
|
71
|
-
maxWidth: '90vw',
|
|
72
|
-
disableClose: false,
|
|
73
|
-
autoFocus: false,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
dialogRef
|
|
77
|
-
.afterClosed()
|
|
78
|
-
.subscribe((result: LabelWidgetState | undefined) => {
|
|
79
|
-
if (result) {
|
|
80
|
-
this.state.set(result);
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
get hasContent(): boolean {
|
|
86
|
-
return !!this.state().label?.trim();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
get label(): string {
|
|
90
|
-
return this.state().label?.trim();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Computed properties for responsive font size limits with fallbacks
|
|
94
|
-
readonly minFontSize = computed(() => this.state().minFontSize ?? 8);
|
|
95
|
-
readonly maxFontSize = computed(() => this.state().maxFontSize ?? 64);
|
|
96
|
-
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
// label-widget.metadata.ts
|
|
2
|
-
export const svgIcon =
|
|
3
|
-
'<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>';
|
package/src/public-api.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Public API Surface of ngx-dashboard-widgets
|
|
3
|
-
*/
|
|
4
|
-
export * from './lib/arrow-widget/arrow-widget.component';
|
|
5
|
-
export * from './lib/label-widget/label-widget.component';
|
|
6
|
-
export * from './lib/clock-widget/clock-widget.component';
|
|
7
|
-
export * from './lib/directives/responsive-text.directive';
|
package/tsconfig.lib.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
-
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
-
{
|
|
4
|
-
"extends": "../../tsconfig.json",
|
|
5
|
-
"compilerOptions": {
|
|
6
|
-
"outDir": "../../out-tsc/lib",
|
|
7
|
-
"declaration": true,
|
|
8
|
-
"declarationMap": true,
|
|
9
|
-
"inlineSources": true,
|
|
10
|
-
"sourceMap": true,
|
|
11
|
-
"types": []
|
|
12
|
-
},
|
|
13
|
-
"include": ["src/**/*.ts"],
|
|
14
|
-
"exclude": ["**/*.spec.ts"]
|
|
15
|
-
}
|
package/tsconfig.lib.prod.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
-
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
-
{
|
|
4
|
-
"extends": "./tsconfig.lib.json",
|
|
5
|
-
"compilerOptions": {
|
|
6
|
-
"declarationMap": false
|
|
7
|
-
},
|
|
8
|
-
"angularCompilerOptions": {
|
|
9
|
-
"compilationMode": "partial"
|
|
10
|
-
}
|
|
11
|
-
}
|
package/tsconfig.spec.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
-
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
-
{
|
|
4
|
-
"extends": "../../tsconfig.json",
|
|
5
|
-
"compilerOptions": {
|
|
6
|
-
"outDir": "../../out-tsc/spec",
|
|
7
|
-
"types": [
|
|
8
|
-
"jasmine"
|
|
9
|
-
]
|
|
10
|
-
},
|
|
11
|
-
"include": [
|
|
12
|
-
"src/**/*.ts"
|
|
13
|
-
]
|
|
14
|
-
}
|