@jjlmoya/utils-hardware 1.17.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/package.json +1 -1
  2. package/src/category/i18n/de.ts +4 -4
  3. package/src/category/i18n/id.ts +3 -3
  4. package/src/category/i18n/it.ts +3 -3
  5. package/src/category/i18n/nl.ts +3 -3
  6. package/src/category/i18n/pl.ts +4 -4
  7. package/src/category/i18n/pt.ts +3 -3
  8. package/src/category/i18n/ru.ts +3 -3
  9. package/src/category/i18n/sv.ts +3 -3
  10. package/src/category/i18n/tr.ts +3 -3
  11. package/src/category/i18n/zh.ts +3 -3
  12. package/src/category/index.ts +2 -1
  13. package/src/entries.ts +4 -1
  14. package/src/index.ts +1 -0
  15. package/src/layouts/PreviewLayout.astro +1 -0
  16. package/src/tests/diacritics_density.test.ts +118 -0
  17. package/src/tests/inverted_punctuation.test.ts +84 -0
  18. package/src/tests/locale_completeness.test.ts +2 -2
  19. package/src/tests/no_en_dash.test.ts +70 -0
  20. package/src/tests/pagespeed_best_practices.test.ts +198 -0
  21. package/src/tests/script_density.test.ts +94 -0
  22. package/src/tests/tool_validation.test.ts +2 -2
  23. package/src/tool/batteryHealthEstimator/component.astro +3 -3
  24. package/src/tool/batteryHealthEstimator/i18n/fr.ts +4 -4
  25. package/src/tool/batteryHealthEstimator/i18n/pl.ts +1 -1
  26. package/src/tool/colorAccuracyTest/color-accuracy-test.css +118 -5
  27. package/src/tool/colorAccuracyTest/i18n/de.ts +7 -7
  28. package/src/tool/colorAccuracyTest/i18n/en.ts +3 -3
  29. package/src/tool/colorAccuracyTest/i18n/es.ts +2 -2
  30. package/src/tool/colorAccuracyTest/i18n/fr.ts +9 -9
  31. package/src/tool/colorAccuracyTest/i18n/id.ts +3 -3
  32. package/src/tool/colorAccuracyTest/i18n/it.ts +3 -3
  33. package/src/tool/colorAccuracyTest/i18n/ja.ts +1 -1
  34. package/src/tool/colorAccuracyTest/i18n/ko.ts +2 -2
  35. package/src/tool/colorAccuracyTest/i18n/nl.ts +3 -3
  36. package/src/tool/colorAccuracyTest/i18n/pl.ts +5 -5
  37. package/src/tool/colorAccuracyTest/i18n/pt.ts +3 -3
  38. package/src/tool/colorAccuracyTest/i18n/ru.ts +15 -15
  39. package/src/tool/colorAccuracyTest/i18n/sv.ts +3 -3
  40. package/src/tool/colorAccuracyTest/i18n/tr.ts +2 -2
  41. package/src/tool/colorAccuracyTest/i18n/zh.ts +7 -7
  42. package/src/tool/deadPixelTest/i18n/ru.ts +6 -6
  43. package/src/tool/deadPixelTest/i18n/sv.ts +1 -1
  44. package/src/tool/deadPixelTest/i18n/zh.ts +5 -5
  45. package/src/tool/gamepadTest/component.astro +4 -3
  46. package/src/tool/gamepadTest/gamepad-test.css +171 -3
  47. package/src/tool/gamepadTest/i18n/es.ts +4 -4
  48. package/src/tool/gamepadTest/i18n/ru.ts +1 -1
  49. package/src/tool/gamepadTest/i18n/zh.ts +1 -1
  50. package/src/tool/gamepadVibrationTester/component.astro +3 -3
  51. package/src/tool/gamepadVibrationTester/i18n/es.ts +1 -1
  52. package/src/tool/gamepadVibrationTester/i18n/fr.ts +2 -2
  53. package/src/tool/keyboardTest/component.astro +6 -1
  54. package/src/tool/keyboardTest/keyboard-test.css +115 -2
  55. package/src/tool/mouseDoubleClickTest/bibliography.astro +14 -0
  56. package/src/tool/mouseDoubleClickTest/bibliography.ts +16 -0
  57. package/src/tool/mouseDoubleClickTest/component.astro +135 -0
  58. package/src/tool/mouseDoubleClickTest/entry.ts +29 -0
  59. package/src/tool/mouseDoubleClickTest/i18n/de.ts +274 -0
  60. package/src/tool/mouseDoubleClickTest/i18n/en.ts +274 -0
  61. package/src/tool/mouseDoubleClickTest/i18n/es.ts +274 -0
  62. package/src/tool/mouseDoubleClickTest/i18n/fr.ts +274 -0
  63. package/src/tool/mouseDoubleClickTest/i18n/id.ts +285 -0
  64. package/src/tool/mouseDoubleClickTest/i18n/it.ts +274 -0
  65. package/src/tool/mouseDoubleClickTest/i18n/ja.ts +274 -0
  66. package/src/tool/mouseDoubleClickTest/i18n/ko.ts +274 -0
  67. package/src/tool/mouseDoubleClickTest/i18n/nl.ts +274 -0
  68. package/src/tool/mouseDoubleClickTest/i18n/pl.ts +274 -0
  69. package/src/tool/mouseDoubleClickTest/i18n/pt.ts +274 -0
  70. package/src/tool/mouseDoubleClickTest/i18n/ru.ts +274 -0
  71. package/src/tool/mouseDoubleClickTest/i18n/sv.ts +274 -0
  72. package/src/tool/mouseDoubleClickTest/i18n/tr.ts +274 -0
  73. package/src/tool/mouseDoubleClickTest/i18n/zh.ts +274 -0
  74. package/src/tool/mouseDoubleClickTest/index.ts +9 -0
  75. package/src/tool/mouseDoubleClickTest/logic.ts +258 -0
  76. package/src/tool/mouseDoubleClickTest/mouse-double-click-test.css +488 -0
  77. package/src/tool/mouseDoubleClickTest/seo.astro +15 -0
  78. package/src/tool/mouseDoubleClickTest/ui.ts +26 -0
  79. package/src/tool/mousePollingTest/i18n/fr.ts +3 -3
  80. package/src/tool/mousePollingTest/i18n/pl.ts +1 -1
  81. package/src/tool/mousePollingTest/i18n/ru.ts +1 -1
  82. package/src/tool/mousePollingTest/i18n/zh.ts +1 -1
  83. package/src/tool/mousePollingTest/logic/RatonManager.ts +6 -6
  84. package/src/tool/refreshRateDetector/i18n/de.ts +3 -3
  85. package/src/tool/refreshRateDetector/i18n/en.ts +3 -3
  86. package/src/tool/refreshRateDetector/i18n/fr.ts +4 -4
  87. package/src/tool/refreshRateDetector/i18n/id.ts +3 -3
  88. package/src/tool/refreshRateDetector/i18n/pl.ts +2 -2
  89. package/src/tool/refreshRateDetector/i18n/pt.ts +3 -3
  90. package/src/tool/refreshRateDetector/i18n/sv.ts +3 -3
  91. package/src/tool/refreshRateDetector/i18n/tr.ts +2 -2
  92. package/src/tool/refreshRateDetector/i18n/zh.ts +2 -2
  93. package/src/tool/toneGenerator/component.astro +7 -7
  94. package/src/tool/toneGenerator/i18n/fr.ts +2 -2
  95. package/src/tool/toneGenerator/i18n/pl.ts +1 -1
  96. package/src/tool/toneGenerator/i18n/ru.ts +2 -2
  97. package/src/tool/toneGenerator/i18n/zh.ts +3 -3
  98. package/src/tools.ts +2 -2
@@ -0,0 +1,258 @@
1
+ type MouseButtonId = 'left' | 'middle' | 'right' | 'back' | 'forward' | 'other';
2
+
3
+ interface ButtonState {
4
+ count: number;
5
+ lastTime: number;
6
+ suspicious: number;
7
+ fastestGap: number;
8
+ }
9
+
10
+ export interface ClickSample {
11
+ button: MouseButtonId;
12
+ label: string;
13
+ gap: number;
14
+ suspicious: boolean;
15
+ }
16
+
17
+ interface MouseDoubleClickElements {
18
+ target: HTMLElement | null;
19
+ total: HTMLElement | null;
20
+ suspicious: HTMLElement | null;
21
+ fastest: HTMLElement | null;
22
+ score: HTMLElement | null;
23
+ status: HTMLElement | null;
24
+ threshold: HTMLInputElement | null;
25
+ thresholdValue: HTMLElement | null;
26
+ lastGap: HTMLElement | null;
27
+ log: HTMLElement | null;
28
+ reset: HTMLElement | null;
29
+ buttons: NodeListOf<HTMLElement>;
30
+ }
31
+
32
+ interface MouseDoubleClickLabels {
33
+ statusIdle: string;
34
+ statusClean: string;
35
+ statusWarning: string;
36
+ emptyLog: string;
37
+ leftButton: string;
38
+ middleButton: string;
39
+ rightButton: string;
40
+ backButton: string;
41
+ forwardButton: string;
42
+ otherButton: string;
43
+ thresholdUnit: string;
44
+ cleanEvent: string;
45
+ suspiciousEvent: string;
46
+ }
47
+
48
+ const BUTTON_FROM_CODE: Record<number, MouseButtonId> = {
49
+ 0: 'left',
50
+ 1: 'middle',
51
+ 2: 'right',
52
+ 3: 'back',
53
+ 4: 'forward',
54
+ };
55
+
56
+ export class MouseDoubleClickTester {
57
+ private threshold = 80;
58
+ private samples: ClickSample[] = [];
59
+ private readonly states = new Map<MouseButtonId, ButtonState>();
60
+
61
+ constructor(
62
+ private readonly elements: MouseDoubleClickElements,
63
+ private readonly labels: MouseDoubleClickLabels,
64
+ ) {
65
+ this.threshold = Number(elements.threshold?.value ?? 80);
66
+ this.bindEvents();
67
+ this.render();
68
+ }
69
+
70
+ private bindEvents() {
71
+ this.elements.target?.addEventListener('pointerdown', (event) => this.recordPointer(event));
72
+ this.elements.target?.addEventListener('contextmenu', (event) => event.preventDefault());
73
+ this.elements.target?.addEventListener('auxclick', (event) => event.preventDefault());
74
+ this.elements.threshold?.addEventListener('input', () => {
75
+ this.threshold = Number(this.elements.threshold?.value ?? 80);
76
+ this.recalculateSuspicion();
77
+ this.render();
78
+ });
79
+ this.elements.reset?.addEventListener('click', () => this.reset());
80
+ }
81
+
82
+ private recordPointer(event: PointerEvent) {
83
+ event.preventDefault();
84
+
85
+ const button = BUTTON_FROM_CODE[event.button] ?? 'other';
86
+ const now = performance.now();
87
+ const state = this.getButtonState(button);
88
+ const gap = state.lastTime > 0 ? now - state.lastTime : 0;
89
+ const suspicious = gap > 0 && gap <= this.threshold;
90
+
91
+ state.count++;
92
+ state.lastTime = now;
93
+
94
+ if (gap > 0) {
95
+ state.fastestGap = Math.min(state.fastestGap, gap);
96
+ if (suspicious) state.suspicious++;
97
+ this.samples.unshift({
98
+ button,
99
+ label: this.getButtonLabel(button),
100
+ gap,
101
+ suspicious,
102
+ });
103
+ }
104
+
105
+ this.flashButton(button, suspicious);
106
+ this.render();
107
+ }
108
+
109
+ private getButtonState(button: MouseButtonId) {
110
+ const existing = this.states.get(button);
111
+ if (existing) return existing;
112
+
113
+ const state: ButtonState = {
114
+ count: 0,
115
+ lastTime: 0,
116
+ suspicious: 0,
117
+ fastestGap: Infinity,
118
+ };
119
+ this.states.set(button, state);
120
+ return state;
121
+ }
122
+
123
+ private recalculateSuspicion() {
124
+ for (const state of this.states.values()) {
125
+ state.suspicious = 0;
126
+ }
127
+
128
+ this.samples = this.samples.map((sample) => {
129
+ const suspicious = sample.gap <= this.threshold;
130
+ if (suspicious) {
131
+ this.getButtonState(sample.button).suspicious++;
132
+ }
133
+ return { ...sample, suspicious };
134
+ });
135
+ }
136
+
137
+ private reset() {
138
+ this.states.clear();
139
+ this.samples = [];
140
+ this.elements.buttons.forEach((button) => {
141
+ button.dataset.count = '0';
142
+ button.dataset.state = '';
143
+ });
144
+ this.render();
145
+ }
146
+
147
+ private getTotals() {
148
+ let total = 0;
149
+ let suspicious = 0;
150
+ let fastestGap = Infinity;
151
+
152
+ for (const state of this.states.values()) {
153
+ total += state.count;
154
+ suspicious += state.suspicious;
155
+ fastestGap = Math.min(fastestGap, state.fastestGap);
156
+ }
157
+
158
+ return { total, suspicious, fastestGap };
159
+ }
160
+
161
+ private getScore() {
162
+ const { total, suspicious } = this.getTotals();
163
+ if (total < 2) return 100;
164
+ const cleanRatio = 1 - suspicious / Math.max(total - 1, 1);
165
+ return Math.max(0, Math.round(cleanRatio * 100));
166
+ }
167
+
168
+ private render() {
169
+ const score = this.getScore();
170
+ const totals = this.getTotals();
171
+ const lastSample = this.samples[0];
172
+
173
+ this.renderMetrics(score, totals, lastSample);
174
+ this.renderButtonCounts();
175
+ this.renderStatus(totals.total, totals.suspicious);
176
+ this.renderLog();
177
+ }
178
+
179
+ private renderMetrics(score: number, totals: ReturnType<MouseDoubleClickTester['getTotals']>, lastSample: ClickSample | undefined) {
180
+ this.setText(this.elements.total, String(totals.total));
181
+ this.setText(this.elements.suspicious, String(totals.suspicious));
182
+ this.setText(this.elements.fastest, this.formatGap(totals.fastestGap));
183
+ this.setText(this.elements.score, `${score}%`);
184
+ this.setText(this.elements.thresholdValue, String(this.threshold));
185
+ this.setText(this.elements.lastGap, lastSample ? `${lastSample.label} ${this.formatGap(lastSample.gap)}` : '--');
186
+ }
187
+
188
+ private setText(element: HTMLElement | null, value: string) {
189
+ if (element) element.textContent = value;
190
+ }
191
+
192
+ private formatGap(gap: number) {
193
+ return Number.isFinite(gap) ? `${Math.round(gap)} ${this.labels.thresholdUnit}` : '--';
194
+ }
195
+
196
+ private renderButtonCounts() {
197
+ this.elements.buttons.forEach((element) => {
198
+ const button = (element.dataset.button ?? 'other') as MouseButtonId;
199
+ const state = this.states.get(button);
200
+ element.dataset.count = String(state?.count ?? 0);
201
+ element.dataset.state = state && state.suspicious > 0 ? 'warning' : '';
202
+ });
203
+ }
204
+
205
+ private renderStatus(total: number, suspicious: number) {
206
+ if (!this.elements.status) return;
207
+
208
+ if (total < 2) {
209
+ this.elements.status.textContent = this.labels.statusIdle;
210
+ this.elements.status.dataset.state = 'clean';
211
+ return;
212
+ }
213
+
214
+ this.elements.status.textContent = suspicious > 0 ? this.labels.statusWarning : this.labels.statusClean;
215
+ this.elements.status.dataset.state = suspicious > 0 ? 'warning' : 'clean';
216
+ }
217
+
218
+ private renderLog() {
219
+ if (!this.elements.log) return;
220
+
221
+ if (this.samples.length === 0) {
222
+ this.elements.log.innerHTML = `<li class="mdct-empty">${this.labels.emptyLog}</li>`;
223
+ return;
224
+ }
225
+
226
+ this.elements.log.innerHTML = this.samples
227
+ .slice(0, 14)
228
+ .map((sample) => {
229
+ const state = sample.suspicious ? 'suspect' : 'clean';
230
+ const label = sample.suspicious ? this.labels.suspiciousEvent : this.labels.cleanEvent;
231
+ return `<li class="mdct-log-item ${state}"><b>${sample.label}</b><span>${Math.round(sample.gap)} ${this.labels.thresholdUnit}</span><em>${label}</em></li>`;
232
+ })
233
+ .join('');
234
+ }
235
+
236
+ private flashButton(button: MouseButtonId, suspicious: boolean) {
237
+ this.elements.target?.setAttribute('data-flash', suspicious ? 'warning' : 'clean');
238
+ const visualButton = Array.from(this.elements.buttons).find((element) => element.dataset.button === button);
239
+ if (visualButton) visualButton.dataset.flash = suspicious ? 'warning' : 'clean';
240
+
241
+ window.setTimeout(() => {
242
+ this.elements.target?.setAttribute('data-flash', '');
243
+ if (visualButton) visualButton.dataset.flash = '';
244
+ }, 180);
245
+ }
246
+
247
+ private getButtonLabel(button: MouseButtonId) {
248
+ const labels: Record<MouseButtonId, string> = {
249
+ left: this.labels.leftButton,
250
+ middle: this.labels.middleButton,
251
+ right: this.labels.rightButton,
252
+ back: this.labels.backButton,
253
+ forward: this.labels.forwardButton,
254
+ other: this.labels.otherButton,
255
+ };
256
+ return labels[button];
257
+ }
258
+ }