@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.
Files changed (34) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +31 -42
  3. package/src/lib/arrow-widget/arrow-state-dialog.component.ts +187 -0
  4. package/src/lib/arrow-widget/arrow-widget.component.html +9 -0
  5. package/src/lib/arrow-widget/arrow-widget.component.scss +52 -0
  6. package/src/lib/arrow-widget/arrow-widget.component.ts +78 -0
  7. package/src/lib/arrow-widget/arrow-widget.metadata.ts +3 -0
  8. package/src/lib/clock-widget/analog-clock/analog-clock.component.html +66 -0
  9. package/src/lib/clock-widget/analog-clock/analog-clock.component.scss +103 -0
  10. package/src/lib/clock-widget/analog-clock/analog-clock.component.ts +120 -0
  11. package/src/lib/clock-widget/clock-state-dialog.component.ts +170 -0
  12. package/src/lib/clock-widget/clock-widget.component.html +16 -0
  13. package/src/lib/clock-widget/clock-widget.component.scss +160 -0
  14. package/src/lib/clock-widget/clock-widget.component.ts +87 -0
  15. package/src/lib/clock-widget/clock-widget.metadata.ts +42 -0
  16. package/src/lib/clock-widget/digital-clock/__tests__/digital-clock.component.spec.ts +276 -0
  17. package/src/lib/clock-widget/digital-clock/digital-clock.component.html +1 -0
  18. package/src/lib/clock-widget/digital-clock/digital-clock.component.scss +43 -0
  19. package/src/lib/clock-widget/digital-clock/digital-clock.component.ts +105 -0
  20. package/src/lib/directives/__tests__/responsive-text.directive.spec.ts +906 -0
  21. package/src/lib/directives/responsive-text.directive.ts +334 -0
  22. package/src/lib/label-widget/__tests__/label-widget.component.spec.ts +539 -0
  23. package/src/lib/label-widget/label-state-dialog.component.ts +385 -0
  24. package/src/lib/label-widget/label-widget.component.html +21 -0
  25. package/src/lib/label-widget/label-widget.component.scss +112 -0
  26. package/src/lib/label-widget/label-widget.component.ts +96 -0
  27. package/src/lib/label-widget/label-widget.metadata.ts +3 -0
  28. package/src/public-api.ts +7 -0
  29. package/tsconfig.lib.json +15 -0
  30. package/tsconfig.lib.prod.json +11 -0
  31. package/tsconfig.spec.json +14 -0
  32. package/fesm2022/dragonworks-ngx-dashboard-widgets.mjs +0 -1255
  33. package/fesm2022/dragonworks-ngx-dashboard-widgets.mjs.map +0 -1
  34. package/index.d.ts +0 -147
@@ -0,0 +1,120 @@
1
+ import { Component, ChangeDetectionStrategy, input, inject, signal, computed, DestroyRef, viewChild, ElementRef, Renderer2, effect } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'ngx-analog-clock',
5
+ standalone: true,
6
+ changeDetection: ChangeDetectionStrategy.OnPush,
7
+ templateUrl: './analog-clock.component.html',
8
+ styleUrl: './analog-clock.component.scss',
9
+ host: {
10
+ '[class.has-background]': 'hasBackground()',
11
+ '[class.show-seconds]': 'showSeconds()',
12
+ 'class': 'clock-widget analog'
13
+ }
14
+ })
15
+ export class AnalogClockComponent {
16
+ readonly #destroyRef = inject(DestroyRef);
17
+ readonly #renderer = inject(Renderer2);
18
+
19
+ // Inputs
20
+ hasBackground = input<boolean>(false);
21
+ showSeconds = input<boolean>(true);
22
+
23
+ // ViewChild references for clock hands
24
+ hourHand = viewChild<ElementRef<SVGPathElement>>('hourHand');
25
+ minuteHand = viewChild<ElementRef<SVGPathElement>>('minuteHand');
26
+ secondHand = viewChild<ElementRef<SVGPathElement>>('secondHand');
27
+
28
+ // Time tracking
29
+ currentTime = signal(new Date());
30
+
31
+ // Computed rotation signals
32
+ secondHandRotation = computed(() => {
33
+ const seconds = this.currentTime().getSeconds();
34
+ return seconds * 6; // 360° / 60s = 6° per second
35
+ });
36
+
37
+ minuteHandRotation = computed(() => {
38
+ const time = this.currentTime();
39
+ const minutes = time.getMinutes();
40
+ const seconds = time.getSeconds();
41
+ return minutes * 6 + seconds / 10; // Smooth minute hand movement
42
+ });
43
+
44
+ hourHandRotation = computed(() => {
45
+ const time = this.currentTime();
46
+ const hours = time.getHours() % 12;
47
+ const minutes = time.getMinutes();
48
+ const seconds = time.getSeconds();
49
+ return hours * 30 + minutes / 2 + seconds / 120; // Smooth hour hand movement
50
+ });
51
+
52
+ #intervalId: number | null = null;
53
+
54
+ constructor() {
55
+ // Set up time update timer
56
+ this.#startTimer();
57
+
58
+ // Clean up timer on component destruction
59
+ this.#destroyRef.onDestroy(() => {
60
+ this.#stopTimer();
61
+ });
62
+
63
+ // Update DOM when rotations change
64
+ effect(() => {
65
+ this.#updateClockHands();
66
+ });
67
+ }
68
+
69
+ #startTimer(): void {
70
+ // Sync to the next second boundary for smooth start
71
+ const now = new Date();
72
+ const msUntilNextSecond = 1000 - now.getMilliseconds();
73
+
74
+ setTimeout(() => {
75
+ this.currentTime.set(new Date());
76
+
77
+ // Start the regular 1-second interval
78
+ this.#intervalId = window.setInterval(() => {
79
+ this.currentTime.set(new Date());
80
+ }, 1000);
81
+ }, msUntilNextSecond);
82
+ }
83
+
84
+ #stopTimer(): void {
85
+ if (this.#intervalId !== null) {
86
+ clearInterval(this.#intervalId);
87
+ this.#intervalId = null;
88
+ }
89
+ }
90
+
91
+ #updateClockHands(): void {
92
+ const hourElement = this.hourHand()?.nativeElement;
93
+ const minuteElement = this.minuteHand()?.nativeElement;
94
+ const secondElement = this.secondHand()?.nativeElement;
95
+
96
+ if (hourElement) {
97
+ this.#renderer.setAttribute(
98
+ hourElement,
99
+ 'transform',
100
+ `rotate(${this.hourHandRotation()}, 400, 400)`
101
+ );
102
+ }
103
+
104
+ if (minuteElement) {
105
+ this.#renderer.setAttribute(
106
+ minuteElement,
107
+ 'transform',
108
+ `rotate(${this.minuteHandRotation()}, 400, 400)`
109
+ );
110
+ }
111
+
112
+ if (secondElement && this.showSeconds()) {
113
+ this.#renderer.setAttribute(
114
+ secondElement,
115
+ 'transform',
116
+ `rotate(${this.secondHandRotation()}, 400, 400)`
117
+ );
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,170 @@
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 { MatRadioModule } from '@angular/material/radio';
11
+ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
12
+ import { ClockWidgetState } from './clock-widget.component';
13
+
14
+ @Component({
15
+ selector: 'demo-clock-state-dialog',
16
+ standalone: true,
17
+ imports: [
18
+ CommonModule,
19
+ FormsModule,
20
+ MatDialogModule,
21
+ MatButtonModule,
22
+ MatRadioModule,
23
+ MatSlideToggleModule,
24
+ ],
25
+ template: `
26
+ <h2 mat-dialog-title>Clock Settings</h2>
27
+ <mat-dialog-content>
28
+ <div class="mode-selection">
29
+ <label class="section-label">Display Mode</label>
30
+ <mat-radio-group
31
+ [value]="mode()"
32
+ (change)="mode.set($any($event.value))"
33
+ >
34
+ <mat-radio-button value="digital">Digital</mat-radio-button>
35
+ <mat-radio-button value="analog">Analog</mat-radio-button>
36
+ </mat-radio-group>
37
+ </div>
38
+
39
+ <!-- Time Format (only for digital mode) -->
40
+ @if (mode() === 'digital') {
41
+ <div class="format-selection">
42
+ <label class="section-label">Time Format</label>
43
+ <mat-radio-group
44
+ [value]="timeFormat()"
45
+ (change)="timeFormat.set($any($event.value))"
46
+ >
47
+ <mat-radio-button value="24h">24 Hour (14:30:45)</mat-radio-button>
48
+ <mat-radio-button value="12h">12 Hour (2:30:45 PM)</mat-radio-button>
49
+ </mat-radio-group>
50
+ </div>
51
+ }
52
+
53
+ <!-- Show Seconds Toggle (for both digital and analog modes) -->
54
+ <div class="toggle-section">
55
+ <mat-slide-toggle
56
+ [checked]="showSeconds()"
57
+ (change)="showSeconds.set($event.checked)">
58
+ Show Seconds
59
+ </mat-slide-toggle>
60
+ <span class="toggle-description">
61
+ @if (mode() === 'digital') {
62
+ Display seconds in the time
63
+ } @else {
64
+ Show the second hand on the clock
65
+ }
66
+ </span>
67
+ </div>
68
+
69
+ <!-- Background Toggle -->
70
+ <div class="toggle-section">
71
+ <mat-slide-toggle
72
+ [checked]="hasBackground()"
73
+ (change)="hasBackground.set($event.checked)">
74
+ Background
75
+ </mat-slide-toggle>
76
+ <span class="toggle-description"
77
+ >Adds a background behind the clock</span
78
+ >
79
+ </div>
80
+ </mat-dialog-content>
81
+
82
+ <mat-dialog-actions align="end">
83
+ <button mat-button (click)="onCancel()">Cancel</button>
84
+ <button mat-flat-button (click)="save()" [disabled]="!hasChanged()">
85
+ Save
86
+ </button>
87
+ </mat-dialog-actions>
88
+ `,
89
+ styles: [
90
+ `
91
+ mat-dialog-content {
92
+ display: block;
93
+ overflow-y: auto;
94
+ overflow-x: hidden;
95
+ }
96
+
97
+ .mode-selection,
98
+ .format-selection {
99
+ margin-top: 1rem;
100
+ margin-bottom: 2rem;
101
+ }
102
+
103
+ .section-label {
104
+ display: block;
105
+ margin-bottom: 0.75rem;
106
+ font-weight: 500;
107
+ }
108
+
109
+ mat-radio-group {
110
+ display: flex;
111
+ flex-direction: column;
112
+ gap: 0.75rem;
113
+ }
114
+
115
+ mat-radio-button {
116
+ margin: 0;
117
+ }
118
+
119
+ /* Toggle section */
120
+ .toggle-section {
121
+ display: flex;
122
+ align-items: center;
123
+ gap: 0.75rem;
124
+ margin-bottom: 0.5rem;
125
+ }
126
+
127
+ .toggle-description {
128
+ margin: 0;
129
+ }
130
+ `,
131
+ ],
132
+ })
133
+ export class ClockStateDialogComponent {
134
+ private readonly data = inject<ClockWidgetState>(MAT_DIALOG_DATA);
135
+ private readonly dialogRef = inject(MatDialogRef<ClockStateDialogComponent>);
136
+
137
+ // State signals
138
+ readonly mode = signal<'analog' | 'digital'>(this.data.mode ?? 'digital');
139
+ readonly hasBackground = signal<boolean>(this.data.hasBackground ?? true);
140
+ readonly timeFormat = signal<'12h' | '24h'>(this.data.timeFormat ?? '24h');
141
+ readonly showSeconds = signal<boolean>(this.data.showSeconds ?? true);
142
+
143
+ // Store original values for comparison
144
+ private readonly originalMode = this.data.mode ?? 'digital';
145
+ private readonly originalHasBackground = this.data.hasBackground ?? true;
146
+ private readonly originalTimeFormat = this.data.timeFormat ?? '24h';
147
+ private readonly originalShowSeconds = this.data.showSeconds ?? true;
148
+
149
+ // Computed values
150
+ readonly hasChanged = computed(
151
+ () =>
152
+ this.mode() !== this.originalMode ||
153
+ this.hasBackground() !== this.originalHasBackground ||
154
+ this.timeFormat() !== this.originalTimeFormat ||
155
+ this.showSeconds() !== this.originalShowSeconds
156
+ );
157
+
158
+ onCancel(): void {
159
+ this.dialogRef.close();
160
+ }
161
+
162
+ save(): void {
163
+ this.dialogRef.close({
164
+ mode: this.mode(),
165
+ hasBackground: this.hasBackground(),
166
+ timeFormat: this.timeFormat(),
167
+ showSeconds: this.showSeconds(),
168
+ } as ClockWidgetState);
169
+ }
170
+ }
@@ -0,0 +1,16 @@
1
+ @if (isDigital) {
2
+ <ngx-digital-clock
3
+ [timeFormat]="state().timeFormat || '24h'"
4
+ [showSeconds]="state().showSeconds ?? true"
5
+ [hasBackground]="state().hasBackground ?? false"
6
+ />
7
+ } @else if (isAnalog) {
8
+ <ngx-analog-clock
9
+ [hasBackground]="state().hasBackground ?? false"
10
+ [showSeconds]="state().showSeconds ?? true"
11
+ />
12
+ } @else {
13
+ <div class="svg-wrapper" [class.has-background]="state().hasBackground">
14
+ <div class="svg-placeholder" [innerHTML]="safeSvgIcon"></div>
15
+ </div>
16
+ }
@@ -0,0 +1,160 @@
1
+ // clock-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 all widget states
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
+ // Clock widget styles
28
+ .clock-widget {
29
+ @extend %widget-base;
30
+ padding: var(--mat-sys-spacing-4);
31
+ color: var(--mat-sys-on-surface-variant, #6c757d);
32
+
33
+ &.has-background {
34
+ color: var(--mat-sys-on-surface, #1f1f1f);
35
+ }
36
+
37
+ &:hover {
38
+ opacity: 0.8;
39
+ color: var(--mat-sys-primary, #6750a4);
40
+ }
41
+ }
42
+
43
+ // Digital clock styles moved to digital-clock.component.scss
44
+
45
+ // Analog clock styles
46
+ .analog-clock {
47
+ width: min(80cqw, 80cqh);
48
+ aspect-ratio: 1 / 1;
49
+ position: relative;
50
+ }
51
+
52
+ .clock-face {
53
+ width: 100%;
54
+ height: 100%;
55
+ border: 2px solid currentColor;
56
+ border-radius: 50%;
57
+ position: relative;
58
+
59
+ &::before,
60
+ &::after {
61
+ content: "";
62
+ position: absolute;
63
+ background-color: currentColor;
64
+ left: 50%;
65
+ transform: translateX(-50%);
66
+ }
67
+
68
+ // 12 o'clock marker
69
+ &::before {
70
+ width: 2px;
71
+ height: 10%;
72
+ top: 0;
73
+ }
74
+
75
+ // 6 o'clock marker
76
+ &::after {
77
+ width: 2px;
78
+ height: 10%;
79
+ bottom: 0;
80
+ }
81
+ }
82
+
83
+ .hour-hand,
84
+ .minute-hand {
85
+ position: absolute;
86
+ background-color: currentColor;
87
+ left: 50%;
88
+ bottom: 50%;
89
+ transform-origin: 50% 100%;
90
+ border-radius: 2px;
91
+ }
92
+
93
+ .hour-hand {
94
+ width: 4px;
95
+ height: 25%;
96
+ transform: translateX(-50%) rotate(30deg); // 1 o'clock position
97
+ }
98
+
99
+ .minute-hand {
100
+ width: 2px;
101
+ height: 35%;
102
+ transform: translateX(-50%) rotate(90deg); // 15 minutes position
103
+ }
104
+
105
+ .center-dot {
106
+ position: absolute;
107
+ width: 8px;
108
+ height: 8px;
109
+ background-color: currentColor;
110
+ border-radius: 50%;
111
+ top: 50%;
112
+ left: 50%;
113
+ transform: translate(-50%, -50%);
114
+ }
115
+
116
+ // SVG placeholder styles
117
+ .svg-wrapper {
118
+ @extend %widget-base;
119
+ overflow: hidden;
120
+ }
121
+
122
+ .svg-placeholder {
123
+ width: min(80cqw, 80cqh);
124
+ aspect-ratio: 1 / 1;
125
+ opacity: 0.3;
126
+ transition: transform 0.3s ease-in-out, opacity 0.3s ease;
127
+ transform-origin: center center;
128
+
129
+ ::ng-deep svg {
130
+ width: 100%;
131
+ height: 100%;
132
+ display: block;
133
+
134
+ // Hour markers and tick marks
135
+ .clock-face {
136
+ stroke: var(--mat-sys-on-surface, #1d1b20);
137
+ }
138
+
139
+ // Clock hands
140
+ .clock-hour-hand {
141
+ fill: var(--mat-sys-on-surface, #1d1b20);
142
+ }
143
+
144
+ .clock-minute-hand {
145
+ fill: var(--mat-sys-on-surface, #1d1b20);
146
+ }
147
+
148
+ .clock-second-hand {
149
+ fill: var(--mat-sys-primary, #6750a4);
150
+ }
151
+ }
152
+
153
+ .has-background & ::ng-deep svg {
154
+ // Background circle (matching live analog clock behavior)
155
+ circle {
156
+ fill: var(--mat-sys-surface, #fffbfe);
157
+ }
158
+ }
159
+ }
160
+
@@ -0,0 +1,87 @@
1
+ // clock-widget.component.ts
2
+ import { Component, inject, signal } from '@angular/core';
3
+ import { Widget, WidgetMetadata } from '@dragonworks/ngx-dashboard';
4
+ import { svgIcon } from './clock-widget.metadata';
5
+ import { DomSanitizer } from '@angular/platform-browser';
6
+ import { ClockStateDialogComponent } from './clock-state-dialog.component';
7
+ import { MatDialog } from '@angular/material/dialog';
8
+ import { DigitalClockComponent } from './digital-clock/digital-clock.component';
9
+ import { AnalogClockComponent } from './analog-clock/analog-clock.component';
10
+
11
+ export interface ClockWidgetState {
12
+ mode: 'analog' | 'digital';
13
+ hasBackground?: boolean;
14
+ timeFormat?: '12h' | '24h';
15
+ showSeconds?: boolean;
16
+ }
17
+
18
+ @Component({
19
+ selector: 'ngx-dashboard-clock-widget',
20
+ standalone: true,
21
+ imports: [DigitalClockComponent, AnalogClockComponent],
22
+ templateUrl: './clock-widget.component.html',
23
+ styleUrl: './clock-widget.component.scss',
24
+ })
25
+ export class ClockWidgetComponent implements Widget {
26
+ static metadata: WidgetMetadata = {
27
+ widgetTypeid: '@default/clock-widget',
28
+ name: 'Clock',
29
+ description: 'Display time in analog or digital format',
30
+ svgIcon,
31
+ };
32
+
33
+ readonly #sanitizer = inject(DomSanitizer);
34
+ readonly #dialog = inject(MatDialog);
35
+
36
+ safeSvgIcon = this.#sanitizer.bypassSecurityTrustHtml(svgIcon);
37
+
38
+ state = signal<ClockWidgetState>({
39
+ mode: 'analog',
40
+ hasBackground: true,
41
+ timeFormat: '24h',
42
+ showSeconds: true,
43
+ });
44
+
45
+ constructor() {
46
+ // No timer logic needed - DigitalClock manages its own time
47
+ }
48
+
49
+ dashboardSetState(state?: unknown) {
50
+ if (state) {
51
+ this.state.update((current) => ({
52
+ ...current,
53
+ ...(state as ClockWidgetState),
54
+ }));
55
+ }
56
+ }
57
+
58
+ dashboardGetState(): ClockWidgetState {
59
+ return { ...this.state() };
60
+ }
61
+
62
+ dashboardEditState(): void {
63
+ const dialogRef = this.#dialog.open(ClockStateDialogComponent, {
64
+ data: this.dashboardGetState(),
65
+ width: '400px',
66
+ maxWidth: '90vw',
67
+ disableClose: false,
68
+ autoFocus: false,
69
+ });
70
+
71
+ dialogRef
72
+ .afterClosed()
73
+ .subscribe((result: ClockWidgetState | undefined) => {
74
+ if (result) {
75
+ this.state.set(result);
76
+ }
77
+ });
78
+ }
79
+
80
+ get isAnalog(): boolean {
81
+ return this.state().mode === 'analog';
82
+ }
83
+
84
+ get isDigital(): boolean {
85
+ return this.state().mode === 'digital';
86
+ }
87
+ }
@@ -0,0 +1,42 @@
1
+ export const svgIcon = `
2
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" preserveAspectRatio="xMidYMid meet">
3
+ <use transform="matrix(-1,0,0,1,800,0)" href="#one-half" />
4
+ <g id="one-half">
5
+ <g id="one-fourth">
6
+ <path d="m400 40v107" stroke-width="26.7" stroke="currentColor" />
7
+ <g id="one-twelfth">
8
+ <path
9
+ d="m580 88.233-42.5 73.612"
10
+ stroke-width="26.7"
11
+ stroke="currentColor"
12
+ />
13
+ <g id="one-thirtieth">
14
+ <path
15
+ id="one-sixtieth"
16
+ d="m437.63 41.974-3.6585 34.808"
17
+ stroke-width="13.6"
18
+ stroke="currentColor"
19
+ />
20
+ <use transform="rotate(6 400 400)" href="#one-sixtieth" />
21
+ </g>
22
+ <use transform="rotate(12 400 400)" href="#one-thirtieth" />
23
+ </g>
24
+ <use transform="rotate(30 400 400)" href="#one-twelfth" />
25
+ <use transform="rotate(60 400 400)" href="#one-twelfth" />
26
+ </g>
27
+ <use transform="rotate(90 400 400)" href="#one-fourth" />
28
+ </g>
29
+ <path
30
+ class="clock-hour-hand"
31
+ id="anim-clock-hour-hand"
32
+ d="m 381.925,476 h 36.15 l 5e-4,-300.03008 L 400,156.25 381.9245,175.96992 Z"
33
+ transform="rotate(110.2650694444, 400, 400)"
34
+ />
35
+ <path
36
+ class="clock-minute-hand"
37
+ id="anim-clock-minute-hand"
38
+ d="M 412.063,496.87456 H 387.937 L 385.249,65.68306 400,52.75 414.751,65.68306 Z"
39
+ transform="rotate(243.1808333333, 400, 400)"
40
+ />
41
+ </svg>
42
+ `;