@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,334 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Directive,
|
|
3
|
-
ElementRef,
|
|
4
|
-
AfterViewInit,
|
|
5
|
-
OnDestroy,
|
|
6
|
-
inject,
|
|
7
|
-
DestroyRef,
|
|
8
|
-
numberAttribute,
|
|
9
|
-
booleanAttribute,
|
|
10
|
-
input,
|
|
11
|
-
} from '@angular/core';
|
|
12
|
-
import { NgZone, PLATFORM_ID } from '@angular/core';
|
|
13
|
-
import { isPlatformBrowser } from '@angular/common';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Directive that automatically adjusts font size to fit text within its parent container.
|
|
17
|
-
* Uses canvas-based measurement for performance and DOM verification for accuracy.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* <div class="container">
|
|
21
|
-
* <span responsiveText [minFontSize]="12" [maxFontSize]="72">Dynamic text here</span>
|
|
22
|
-
* </div>
|
|
23
|
-
*/
|
|
24
|
-
@Directive({
|
|
25
|
-
selector: '[responsiveText]',
|
|
26
|
-
standalone: true,
|
|
27
|
-
host: {
|
|
28
|
-
'[style.display]': '"block"',
|
|
29
|
-
'[style.width]': '"100%"',
|
|
30
|
-
'[style.white-space]': '"nowrap"',
|
|
31
|
-
'[style.overflow]': '"visible"',
|
|
32
|
-
},
|
|
33
|
-
})
|
|
34
|
-
export class ResponsiveTextDirective implements AfterViewInit, OnDestroy {
|
|
35
|
-
/* ───────────────────────── Inputs with transforms ─────────────── */
|
|
36
|
-
/** Minimum font-size in pixels (accessibility floor) */
|
|
37
|
-
minFontSize = input(8, { transform: numberAttribute });
|
|
38
|
-
|
|
39
|
-
/** Maximum font-size in pixels (layout ceiling) */
|
|
40
|
-
maxFontSize = input(512, { transform: numberAttribute });
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Line-height: pass a multiplier (e.g. 1.1) or absolute px value.
|
|
44
|
-
* For single-line text a multiplier < 10 is treated as unitless.
|
|
45
|
-
*/
|
|
46
|
-
lineHeight = input(1.1, { transform: numberAttribute });
|
|
47
|
-
|
|
48
|
-
/** Whether to observe text mutations after first render */
|
|
49
|
-
observeMutations = input(true, { transform: booleanAttribute });
|
|
50
|
-
|
|
51
|
-
/** Debounce delay in ms for resize/mutation callbacks */
|
|
52
|
-
debounceMs = input(16, { transform: numberAttribute });
|
|
53
|
-
|
|
54
|
-
/* ───────────────────────── Private state ───────────────────────── */
|
|
55
|
-
private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
56
|
-
private readonly zone = inject(NgZone);
|
|
57
|
-
private readonly platformId = inject(PLATFORM_ID);
|
|
58
|
-
private readonly destroyRef = inject(DestroyRef);
|
|
59
|
-
|
|
60
|
-
// Canvas context - lazy initialization
|
|
61
|
-
private _ctx?: CanvasRenderingContext2D;
|
|
62
|
-
private get ctx(): CanvasRenderingContext2D {
|
|
63
|
-
if (!this._ctx) {
|
|
64
|
-
const canvas = document.createElement('canvas');
|
|
65
|
-
this._ctx = canvas.getContext('2d', {
|
|
66
|
-
willReadFrequently: true,
|
|
67
|
-
alpha: false,
|
|
68
|
-
})!;
|
|
69
|
-
}
|
|
70
|
-
return this._ctx;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private ro?: ResizeObserver;
|
|
74
|
-
private mo?: MutationObserver;
|
|
75
|
-
private fitTimeout?: number;
|
|
76
|
-
|
|
77
|
-
// Cache for performance
|
|
78
|
-
private lastText = '';
|
|
79
|
-
private lastMaxW = 0;
|
|
80
|
-
private lastMaxH = 0;
|
|
81
|
-
private lastFontSize = 0;
|
|
82
|
-
|
|
83
|
-
/* ───────────────────────── Lifecycle ──────────────────────────── */
|
|
84
|
-
ngAfterViewInit() {
|
|
85
|
-
if (!isPlatformBrowser(this.platformId)) return;
|
|
86
|
-
|
|
87
|
-
// Set initial styles
|
|
88
|
-
const span = this.el.nativeElement;
|
|
89
|
-
span.style.transition = 'font-size 0.1s ease-out';
|
|
90
|
-
|
|
91
|
-
// All observer callbacks run outside Angular's zone
|
|
92
|
-
this.zone.runOutsideAngular(() => {
|
|
93
|
-
this.fit();
|
|
94
|
-
this.observeResize();
|
|
95
|
-
if (this.observeMutations()) {
|
|
96
|
-
this.observeText();
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
ngOnDestroy() {
|
|
102
|
-
this.cleanup();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/* ───────────────────── Core fitting logic ───────────────────── */
|
|
106
|
-
/**
|
|
107
|
-
* Debounced fit handler to prevent excessive recalculations
|
|
108
|
-
*/
|
|
109
|
-
private requestFit = () => {
|
|
110
|
-
if (this.fitTimeout) {
|
|
111
|
-
cancelAnimationFrame(this.fitTimeout);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
this.fitTimeout = requestAnimationFrame(() => {
|
|
115
|
-
this.fit();
|
|
116
|
-
});
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Recalculate & apply the ideal font-size
|
|
121
|
-
*/
|
|
122
|
-
private fit = () => {
|
|
123
|
-
const span = this.el.nativeElement;
|
|
124
|
-
const parent = span.parentElement;
|
|
125
|
-
|
|
126
|
-
if (!parent) return;
|
|
127
|
-
|
|
128
|
-
const text = span.textContent?.trim() || '';
|
|
129
|
-
if (!text) {
|
|
130
|
-
span.style.fontSize = `${this.minFontSize()}px`;
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const { maxW, maxH } = this.getAvailableSpace(parent);
|
|
135
|
-
|
|
136
|
-
// Check cache to avoid redundant calculations
|
|
137
|
-
if (
|
|
138
|
-
text === this.lastText &&
|
|
139
|
-
maxW === this.lastMaxW &&
|
|
140
|
-
maxH === this.lastMaxH &&
|
|
141
|
-
this.lastFontSize > 0
|
|
142
|
-
) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Calculate with conservative buffer for sub-pixel accuracy
|
|
147
|
-
const ideal = this.calcFit(text, maxW * 0.98, maxH * 0.98);
|
|
148
|
-
|
|
149
|
-
span.style.fontSize = `${ideal}px`;
|
|
150
|
-
|
|
151
|
-
// DOM verification pass
|
|
152
|
-
this.verifyFit(span, maxW, maxH, ideal);
|
|
153
|
-
|
|
154
|
-
// Update cache
|
|
155
|
-
this.lastText = text;
|
|
156
|
-
this.lastMaxW = maxW;
|
|
157
|
-
this.lastMaxH = maxH;
|
|
158
|
-
this.lastFontSize = parseFloat(span.style.fontSize);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Calculate available space accounting for padding and borders
|
|
163
|
-
*/
|
|
164
|
-
private getAvailableSpace(parent: HTMLElement): {
|
|
165
|
-
maxW: number;
|
|
166
|
-
maxH: number;
|
|
167
|
-
} {
|
|
168
|
-
const cs = getComputedStyle(parent);
|
|
169
|
-
const maxW =
|
|
170
|
-
parent.clientWidth -
|
|
171
|
-
parseFloat(cs.paddingLeft) -
|
|
172
|
-
parseFloat(cs.paddingRight);
|
|
173
|
-
const maxH =
|
|
174
|
-
parent.clientHeight -
|
|
175
|
-
parseFloat(cs.paddingTop) -
|
|
176
|
-
parseFloat(cs.paddingBottom);
|
|
177
|
-
|
|
178
|
-
return { maxW: Math.max(0, maxW), maxH: Math.max(0, maxH) };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* DOM-based verification to handle sub-pixel discrepancies
|
|
183
|
-
*/
|
|
184
|
-
private verifyFit(
|
|
185
|
-
span: HTMLElement,
|
|
186
|
-
maxW: number,
|
|
187
|
-
maxH: number,
|
|
188
|
-
ideal: number
|
|
189
|
-
) {
|
|
190
|
-
// Simple synchronous verification
|
|
191
|
-
if (span.scrollWidth > maxW || span.scrollHeight > maxH) {
|
|
192
|
-
let safe = ideal;
|
|
193
|
-
let iterations = 0;
|
|
194
|
-
const maxIterations = 10;
|
|
195
|
-
|
|
196
|
-
while (
|
|
197
|
-
iterations < maxIterations &&
|
|
198
|
-
safe > this.minFontSize() &&
|
|
199
|
-
(span.scrollWidth > maxW || span.scrollHeight > maxH)
|
|
200
|
-
) {
|
|
201
|
-
safe -= 0.25;
|
|
202
|
-
span.style.fontSize = `${safe}px`;
|
|
203
|
-
iterations++;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Update cache with verified size
|
|
207
|
-
this.lastFontSize = safe;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/* ───────────────────── Binary search algorithm ────────────────── */
|
|
212
|
-
/**
|
|
213
|
-
* Binary search for optimal font size using canvas measurements
|
|
214
|
-
*/
|
|
215
|
-
private calcFit(
|
|
216
|
-
text: string,
|
|
217
|
-
maxW: number,
|
|
218
|
-
maxH: number,
|
|
219
|
-
precision = 0.1
|
|
220
|
-
): number {
|
|
221
|
-
if (maxW <= 0 || maxH <= 0) return this.minFontSize();
|
|
222
|
-
|
|
223
|
-
const computedStyle = getComputedStyle(this.el.nativeElement);
|
|
224
|
-
const fontFamily = computedStyle.fontFamily || 'sans-serif';
|
|
225
|
-
const fontWeight = computedStyle.fontWeight || '400';
|
|
226
|
-
|
|
227
|
-
let lo = this.minFontSize();
|
|
228
|
-
let hi = this.maxFontSize();
|
|
229
|
-
let bestFit = this.minFontSize();
|
|
230
|
-
|
|
231
|
-
while (hi - lo > precision) {
|
|
232
|
-
const mid = (hi + lo) / 2;
|
|
233
|
-
this.ctx.font = `${fontWeight} ${mid}px ${fontFamily}`;
|
|
234
|
-
|
|
235
|
-
const metrics = this.ctx.measureText(text);
|
|
236
|
-
const width = metrics.width;
|
|
237
|
-
|
|
238
|
-
// Calculate height based on available metrics
|
|
239
|
-
const height = this.calculateTextHeight(metrics, mid);
|
|
240
|
-
|
|
241
|
-
if (width <= maxW && height <= maxH) {
|
|
242
|
-
bestFit = mid;
|
|
243
|
-
lo = mid;
|
|
244
|
-
} else {
|
|
245
|
-
hi = mid;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return Math.floor(bestFit * 100) / 100;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Calculate text height from metrics
|
|
254
|
-
*/
|
|
255
|
-
private calculateTextHeight(metrics: TextMetrics, fontSize: number): number {
|
|
256
|
-
// Use font bounding box metrics if available
|
|
257
|
-
if (metrics.fontBoundingBoxAscent && metrics.fontBoundingBoxDescent) {
|
|
258
|
-
return metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Fallback to actual bounding box
|
|
262
|
-
if (metrics.actualBoundingBoxAscent && metrics.actualBoundingBoxDescent) {
|
|
263
|
-
return metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Final fallback using line height
|
|
267
|
-
return this.lineHeight() < 10
|
|
268
|
-
? fontSize * this.lineHeight()
|
|
269
|
-
: this.lineHeight();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/* ───────────────────────── Observers ─────────────────────────── */
|
|
273
|
-
/**
|
|
274
|
-
* Observe parent container resizes
|
|
275
|
-
*/
|
|
276
|
-
private observeResize() {
|
|
277
|
-
if (!('ResizeObserver' in window)) return;
|
|
278
|
-
|
|
279
|
-
this.ro = new ResizeObserver((entries) => {
|
|
280
|
-
// Only trigger if size actually changed
|
|
281
|
-
const entry = entries[0];
|
|
282
|
-
if (entry?.contentRect) {
|
|
283
|
-
this.requestFit();
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
const parent = this.el.nativeElement.parentElement;
|
|
288
|
-
if (parent) {
|
|
289
|
-
this.ro.observe(parent);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Observe text content changes
|
|
295
|
-
*/
|
|
296
|
-
private observeText() {
|
|
297
|
-
if (!('MutationObserver' in window)) return;
|
|
298
|
-
|
|
299
|
-
this.mo = new MutationObserver((mutations) => {
|
|
300
|
-
// Check if text actually changed
|
|
301
|
-
const hasTextChange = mutations.some(
|
|
302
|
-
(m) =>
|
|
303
|
-
m.type === 'characterData' ||
|
|
304
|
-
(m.type === 'childList' &&
|
|
305
|
-
(m.addedNodes.length > 0 || m.removedNodes.length > 0))
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
if (hasTextChange) {
|
|
309
|
-
this.requestFit();
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
this.mo.observe(this.el.nativeElement, {
|
|
314
|
-
characterData: true,
|
|
315
|
-
childList: true,
|
|
316
|
-
subtree: true,
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Cleanup resources
|
|
322
|
-
*/
|
|
323
|
-
private cleanup() {
|
|
324
|
-
this.ro?.disconnect();
|
|
325
|
-
this.mo?.disconnect();
|
|
326
|
-
|
|
327
|
-
if (this.fitTimeout) {
|
|
328
|
-
cancelAnimationFrame(this.fitTimeout);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Clear canvas context
|
|
332
|
-
this._ctx = undefined;
|
|
333
|
-
}
|
|
334
|
-
}
|