gaugeit.js 0.1.0 → 0.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/README.md +51 -27
- package/SECURITY.md +2 -2
- package/dist/gaugeit.cjs +158 -104
- package/dist/gaugeit.cjs.map +3 -3
- package/dist/gaugeit.css +32 -20
- package/dist/gaugeit.d.ts +17 -0
- package/dist/gaugeit.esm.js +158 -104
- package/dist/gaugeit.esm.js.map +3 -3
- package/dist/gaugeit.esm.min.js +2 -2
- package/dist/gaugeit.esm.min.js.map +3 -3
- package/dist/gaugeit.min.cjs +2 -2
- package/dist/gaugeit.min.cjs.map +3 -3
- package/dist/gaugeit.min.css +1 -1
- package/dist/gaugeit.standalone.js +159 -105
- package/dist/gaugeit.standalone.js.map +3 -3
- package/dist/gaugeit.standalone.min.js +35 -23
- package/dist/gaugeit.standalone.min.js.map +3 -3
- package/dist/gaugeit.umd.js +158 -104
- package/dist/gaugeit.umd.js.map +3 -3
- package/dist/gaugeit.umd.min.js +2 -2
- package/dist/gaugeit.umd.min.js.map +3 -3
- package/dist/manifest.json +20 -20
- package/docs/ai/ai-full.md +5097 -0
- package/docs/ai/contributor-guide.md +928 -0
- package/docs/ai/index.md +73 -0
- package/docs/ai/integration-recipes.md +1117 -0
- package/docs/ai/library-usage.md +875 -0
- package/docs/ai/options-reference.md +1107 -0
- package/docs/ai/troubleshooting.md +1024 -0
- package/docs/api.md +34 -7
- package/docs/architecture.md +10 -2
- package/docs/creating-gauge-types.md +2 -1
- package/docs/development.md +2 -2
- package/docs/framework-integration.md +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,5097 @@
|
|
|
1
|
+
# Gaugeit.js 0.1.1 complete AI context
|
|
2
|
+
|
|
3
|
+
This file is the single-file context bundle for an AI that cannot navigate several
|
|
4
|
+
repository documents. It combines the detailed Gaugeit.js AI usage, option,
|
|
5
|
+
integration, contributor, and troubleshooting guides.
|
|
6
|
+
|
|
7
|
+
Do not auto-load this file when root `AGENTS.md` and one focused guide are sufficient.
|
|
8
|
+
It is intentionally large. The canonical implementation remains in `src/`, canonical
|
|
9
|
+
core declarations in `src/index.d.ts`, React adapter declarations in
|
|
10
|
+
`adapters/react/Gaugeit.d.ts`, and executable behavior in `tests/`.
|
|
11
|
+
|
|
12
|
+
Package identity:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
npm: gaugeit.js
|
|
16
|
+
Composer: kasperi/gaugeit-js
|
|
17
|
+
browser global: Gaugeit
|
|
18
|
+
React component: Gaugeit
|
|
19
|
+
version described: 0.1.1
|
|
20
|
+
repository: https://github.com/kasperikoski/gaugeit.js
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Contents:
|
|
24
|
+
|
|
25
|
+
1. Library usage
|
|
26
|
+
2. Complete option reference
|
|
27
|
+
3. Integration recipes
|
|
28
|
+
4. Contributor architecture and workflow
|
|
29
|
+
5. Troubleshooting
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
# Part I — Library usage
|
|
34
|
+
|
|
35
|
+
This guide is written for an AI that must generate working Gaugeit.js 0.1.1
|
|
36
|
+
integration code without guessing. It focuses on selecting the correct distribution,
|
|
37
|
+
creating and updating gauges, integrating lifecycle ownership, and avoiding the most
|
|
38
|
+
common package and browser mistakes.
|
|
39
|
+
|
|
40
|
+
For individual option fields, read `options-reference.md`. For complete copy-ready
|
|
41
|
+
files, read `integration-recipes.md`.
|
|
42
|
+
|
|
43
|
+
## Fast decision tree
|
|
44
|
+
|
|
45
|
+
Use this decision order:
|
|
46
|
+
|
|
47
|
+
1. **React application?**
|
|
48
|
+
- Import the component from `gaugeit.js/react`.
|
|
49
|
+
- Import `gaugeit.js/css` once.
|
|
50
|
+
- Keep frequently recreated option objects stable with `useMemo`.
|
|
51
|
+
- Use a ref when imperative instance methods are needed.
|
|
52
|
+
|
|
53
|
+
2. **Bundler or native ESM application?**
|
|
54
|
+
- Import from `gaugeit.js`.
|
|
55
|
+
- Import `gaugeit.js/css`.
|
|
56
|
+
- In a browser without a package resolver, import `dist/gaugeit.esm.js` by URL and
|
|
57
|
+
include `dist/gaugeit.css`.
|
|
58
|
+
|
|
59
|
+
3. **CommonJS application?**
|
|
60
|
+
- `require('gaugeit.js')`.
|
|
61
|
+
- Ensure the application also loads or bundles the CSS.
|
|
62
|
+
|
|
63
|
+
4. **Plain HTML with two cacheable files?**
|
|
64
|
+
- Include `gaugeit.min.css`.
|
|
65
|
+
- Include `gaugeit.umd.min.js`.
|
|
66
|
+
- Use the global `Gaugeit`.
|
|
67
|
+
|
|
68
|
+
5. **Plain HTML that must use one JavaScript file?**
|
|
69
|
+
- Include `gaugeit.standalone.min.js`.
|
|
70
|
+
- Do not add separate Gaugeit CSS unless intentionally overriding it.
|
|
71
|
+
- Use the global `Gaugeit`.
|
|
72
|
+
|
|
73
|
+
6. **PHP or Composer-managed server-rendered application?**
|
|
74
|
+
- Install `kasperi/gaugeit-js`.
|
|
75
|
+
- Run `vendor/bin/gaugeit-install-assets`.
|
|
76
|
+
- Include the copied CSS and UMD bundle from a public path.
|
|
77
|
+
- Render normal host elements and initialize in browser JavaScript.
|
|
78
|
+
|
|
79
|
+
7. **Declarative custom element?**
|
|
80
|
+
- Load a normal Gaugeit distribution.
|
|
81
|
+
- Call `Gaugeit.defineGaugeitElement()` or import
|
|
82
|
+
`defineGaugeitElement` from `gaugeit.js/web-component`.
|
|
83
|
+
- Use `<gauge-it>` attributes.
|
|
84
|
+
|
|
85
|
+
8. **Declarative data attributes?**
|
|
86
|
+
- Add `data-gaugeit`.
|
|
87
|
+
- Load a normal Gaugeit distribution.
|
|
88
|
+
- Call `Gaugeit.autoMount()` after the elements exist.
|
|
89
|
+
|
|
90
|
+
## Package and distribution matrix
|
|
91
|
+
|
|
92
|
+
| Environment | JavaScript | CSS | API name |
|
|
93
|
+
| --- | --- | --- | --- |
|
|
94
|
+
| npm ESM | `gaugeit.js` | `gaugeit.js/css` | named exports or default `Gaugeit` |
|
|
95
|
+
| npm CommonJS | `require('gaugeit.js')` | load separately | returned exports |
|
|
96
|
+
| npm React | `gaugeit.js/react` | `gaugeit.js/css` | component `Gaugeit` |
|
|
97
|
+
| npm custom element | `gaugeit.js/web-component` | `gaugeit.js/css` | `defineGaugeitElement` |
|
|
98
|
+
| Browser UMD | `dist/gaugeit.umd.min.js` | `dist/gaugeit.min.css` | `window.Gaugeit` |
|
|
99
|
+
| Browser standalone | `dist/gaugeit.standalone.min.js` | embedded | `window.Gaugeit` |
|
|
100
|
+
| Browser ESM | `dist/gaugeit.esm.js` | `dist/gaugeit.css` | module exports |
|
|
101
|
+
| Composer | copied UMD and CSS | copied CSS | `window.Gaugeit` |
|
|
102
|
+
|
|
103
|
+
The UMD build does **not** inject CSS. The standalone build injects canonical CSS once
|
|
104
|
+
using the stable `#gaugeit-styles` marker.
|
|
105
|
+
|
|
106
|
+
## Installation
|
|
107
|
+
|
|
108
|
+
### npm
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm install gaugeit.js@0.1.1
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Recommended ESM usage:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
import { createGauge } from 'gaugeit.js';
|
|
118
|
+
import 'gaugeit.js/css';
|
|
119
|
+
|
|
120
|
+
const gauge = createGauge('#speed', {
|
|
121
|
+
type: 'arc',
|
|
122
|
+
min: 0,
|
|
123
|
+
max: 240,
|
|
124
|
+
value: 88
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Default-object usage:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
import Gaugeit from 'gaugeit.js';
|
|
132
|
+
import 'gaugeit.js/css';
|
|
133
|
+
|
|
134
|
+
const gauge = Gaugeit.createGauge('#speed', {
|
|
135
|
+
type: 'classic',
|
|
136
|
+
value: 42
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### CommonJS
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
const { createGauge } = require('gaugeit.js');
|
|
144
|
+
|
|
145
|
+
const gauge = createGauge(document.querySelector('#speed'), {
|
|
146
|
+
type: 'line-scale',
|
|
147
|
+
value: 42
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
CommonJS resolves the JavaScript entry. CSS still belongs to the consuming application's
|
|
152
|
+
asset pipeline or page.
|
|
153
|
+
|
|
154
|
+
### Native browser ESM
|
|
155
|
+
|
|
156
|
+
```html
|
|
157
|
+
<link rel="stylesheet" href="./dist/gaugeit.css">
|
|
158
|
+
<div id="temperature"></div>
|
|
159
|
+
|
|
160
|
+
<script type="module">
|
|
161
|
+
import { createGauge } from './dist/gaugeit.esm.js';
|
|
162
|
+
|
|
163
|
+
createGauge('#temperature', {
|
|
164
|
+
type: 'classic',
|
|
165
|
+
min: -20,
|
|
166
|
+
max: 120,
|
|
167
|
+
value: 68
|
|
168
|
+
});
|
|
169
|
+
</script>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
A native browser cannot resolve the bare package specifier `gaugeit.js` unless an
|
|
173
|
+
import map, bundler, or server-side package resolver is present.
|
|
174
|
+
|
|
175
|
+
### UMD with separate CSS
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
179
|
+
|
|
180
|
+
<div id="score"></div>
|
|
181
|
+
|
|
182
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
183
|
+
<script>
|
|
184
|
+
const gauge = Gaugeit.createGauge('#score', {
|
|
185
|
+
type: 'arc',
|
|
186
|
+
min: 0,
|
|
187
|
+
max: 100,
|
|
188
|
+
value: 64
|
|
189
|
+
});
|
|
190
|
+
</script>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The global name is exactly `Gaugeit`. It is not `GaugeIt`, `GaugeIT`, or `gaugeit`.
|
|
194
|
+
|
|
195
|
+
### Standalone one-file browser build
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<div id="score"></div>
|
|
199
|
+
|
|
200
|
+
<script src="./dist/gaugeit.standalone.min.js"></script>
|
|
201
|
+
<script>
|
|
202
|
+
Gaugeit.createGauge('#score', {
|
|
203
|
+
type: 'classic',
|
|
204
|
+
value: 64
|
|
205
|
+
});
|
|
206
|
+
</script>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Standalone is appropriate when one JavaScript file is more important than separately
|
|
210
|
+
cacheable CSS. It contains the same public API as UMD.
|
|
211
|
+
|
|
212
|
+
### jsDelivr
|
|
213
|
+
|
|
214
|
+
Use exact versions in production:
|
|
215
|
+
|
|
216
|
+
```html
|
|
217
|
+
<link
|
|
218
|
+
rel="stylesheet"
|
|
219
|
+
href="https://cdn.jsdelivr.net/npm/gaugeit.js@0.1.1/dist/gaugeit.min.css"
|
|
220
|
+
>
|
|
221
|
+
<script
|
|
222
|
+
src="https://cdn.jsdelivr.net/npm/gaugeit.js@0.1.1/dist/gaugeit.umd.min.js"
|
|
223
|
+
></script>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
One-file form:
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
<script
|
|
230
|
+
src="https://cdn.jsdelivr.net/npm/gaugeit.js@0.1.1/dist/gaugeit.standalone.min.js"
|
|
231
|
+
></script>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### UNPKG
|
|
235
|
+
|
|
236
|
+
```html
|
|
237
|
+
<link
|
|
238
|
+
rel="stylesheet"
|
|
239
|
+
href="https://unpkg.com/gaugeit.js@0.1.1/dist/gaugeit.min.css"
|
|
240
|
+
>
|
|
241
|
+
<script
|
|
242
|
+
src="https://unpkg.com/gaugeit.js@0.1.1/dist/gaugeit.umd.min.js"
|
|
243
|
+
></script>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
One-file form:
|
|
247
|
+
|
|
248
|
+
```html
|
|
249
|
+
<script
|
|
250
|
+
src="https://unpkg.com/gaugeit.js@0.1.1/dist/gaugeit.standalone.min.js"
|
|
251
|
+
></script>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Do not omit the version in production examples. An unpinned CDN URL may change after a
|
|
255
|
+
new release.
|
|
256
|
+
|
|
257
|
+
### Composer
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
composer require kasperi/gaugeit-js:^0.1
|
|
261
|
+
vendor/bin/gaugeit-install-assets public/assets/vendor/gaugeit
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Default installed assets:
|
|
265
|
+
|
|
266
|
+
```text
|
|
267
|
+
gaugeit.umd.min.js
|
|
268
|
+
gaugeit.min.css
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Other modes:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
vendor/bin/gaugeit-install-assets --standalone public/assets/vendor/gaugeit
|
|
275
|
+
vendor/bin/gaugeit-install-assets --all public/assets/vendor/gaugeit
|
|
276
|
+
vendor/bin/gaugeit-install-assets --clean public/assets/vendor/gaugeit
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`--clean` removes only known Gaugeit.js assets from the destination. It preserves
|
|
280
|
+
unrelated files. Composer consumers use prebuilt assets and do not need Node.js.
|
|
281
|
+
|
|
282
|
+
Gaugeit.js does not write into an application's public directory automatically during
|
|
283
|
+
dependency installation. The consuming project owns the explicit asset-copy command.
|
|
284
|
+
|
|
285
|
+
## Built-in types
|
|
286
|
+
|
|
287
|
+
Use one of these exact names:
|
|
288
|
+
|
|
289
|
+
| Type | Geometry family | Intended use |
|
|
290
|
+
| --- | --- | --- |
|
|
291
|
+
| `arc` | radial | lightweight semicircular zones and pointer |
|
|
292
|
+
| `classic` | radial | framed vintage analog instrument |
|
|
293
|
+
| `heritage-round` | radial | circular glass-fronted 270-degree instrument |
|
|
294
|
+
| `line-scale` | radial | dense configurable tick-focused scale |
|
|
295
|
+
| `classic-linear` | linear | framed straight scale with moving indicator |
|
|
296
|
+
| `classic-linear-zero` | linear | signed straight scale with fixed zero marker |
|
|
297
|
+
| `center-zero` | radial | signed heritage dial around a center reference |
|
|
298
|
+
| `rolling-tape` | tape | moving numeric strip behind a fixed reference line |
|
|
299
|
+
|
|
300
|
+
Do not infer geometry solely from the visual name. The selected type supplies the
|
|
301
|
+
correct geometry mode and layer family.
|
|
302
|
+
|
|
303
|
+
## Minimal creation
|
|
304
|
+
|
|
305
|
+
The target may be an element or a CSS selector:
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
const gauge = Gaugeit.createGauge('#gauge', {
|
|
309
|
+
type: 'arc',
|
|
310
|
+
value: 72
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Equivalent element form:
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
const host = document.querySelector('#gauge');
|
|
318
|
+
const gauge = Gaugeit.createGauge(host, {
|
|
319
|
+
type: 'arc',
|
|
320
|
+
value: 72
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The target must exist before creation. Creating another managed gauge on the same host
|
|
325
|
+
destroys the previous managed instance.
|
|
326
|
+
|
|
327
|
+
## A practical configuration pattern
|
|
328
|
+
|
|
329
|
+
```js
|
|
330
|
+
const gauge = Gaugeit.createGauge('#engine-load', {
|
|
331
|
+
type: 'arc',
|
|
332
|
+
min: 0,
|
|
333
|
+
max: 100,
|
|
334
|
+
value: 38,
|
|
335
|
+
zones: [
|
|
336
|
+
{ min: 0, max: 60, color: '#22c55e' },
|
|
337
|
+
{ min: 60, max: 85, color: '#facc15' },
|
|
338
|
+
{ min: 85, max: 100, color: '#ef4444' }
|
|
339
|
+
],
|
|
340
|
+
readout: {
|
|
341
|
+
title: {
|
|
342
|
+
visible: true,
|
|
343
|
+
text: 'ENGINE LOAD'
|
|
344
|
+
},
|
|
345
|
+
value: {
|
|
346
|
+
visible: true,
|
|
347
|
+
suffix: '%'
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
accessibility: {
|
|
351
|
+
label: 'Engine load',
|
|
352
|
+
valueText: (value) => `${Math.round(value)} percent`
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Prefer normal option objects over DOM manipulation of generated SVG.
|
|
358
|
+
|
|
359
|
+
## Updating values
|
|
360
|
+
|
|
361
|
+
Use `setValue()` for ordinary numeric updates:
|
|
362
|
+
|
|
363
|
+
```js
|
|
364
|
+
gauge.setValue(76);
|
|
365
|
+
gauge.setValue(18, {
|
|
366
|
+
duration: 1400,
|
|
367
|
+
easing: 'easeInOutCubic'
|
|
368
|
+
});
|
|
369
|
+
gauge.setValue(50, {
|
|
370
|
+
animate: false
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
`set()` is an alias:
|
|
375
|
+
|
|
376
|
+
```js
|
|
377
|
+
gauge.set(64);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Do not call `updateOptions({ value })` for a high-frequency value stream unless the
|
|
381
|
+
configuration must also be rebuilt. `setValue()` updates dynamic controllers without
|
|
382
|
+
re-rendering static layers.
|
|
383
|
+
|
|
384
|
+
## Updating options
|
|
385
|
+
|
|
386
|
+
Deep-merge a partial patch:
|
|
387
|
+
|
|
388
|
+
```js
|
|
389
|
+
gauge.updateOptions({
|
|
390
|
+
pointer: {
|
|
391
|
+
type: 'line',
|
|
392
|
+
color: '#dc2626'
|
|
393
|
+
},
|
|
394
|
+
labels: {
|
|
395
|
+
fractionDigits: 1
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`setOptions()` is an alias for `updateOptions()`.
|
|
401
|
+
|
|
402
|
+
Replace all earlier instance overrides:
|
|
403
|
+
|
|
404
|
+
```js
|
|
405
|
+
gauge.replaceOptions({
|
|
406
|
+
type: 'classic',
|
|
407
|
+
min: 0,
|
|
408
|
+
max: 100,
|
|
409
|
+
value: 42
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Arrays are replaced, not concatenated. Therefore:
|
|
414
|
+
|
|
415
|
+
```js
|
|
416
|
+
gauge.updateOptions({
|
|
417
|
+
zones: [
|
|
418
|
+
{ min: 0, max: 100, color: '#22c55e' }
|
|
419
|
+
]
|
|
420
|
+
});
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
replaces the previous complete zone list.
|
|
424
|
+
|
|
425
|
+
When `updateOptions()` or `replaceOptions()` includes `value`, the value is applied
|
|
426
|
+
immediately by default. Pass `{ animateToValue: true }` as the second argument to
|
|
427
|
+
animate after the rebuild:
|
|
428
|
+
|
|
429
|
+
```js
|
|
430
|
+
gauge.updateOptions(
|
|
431
|
+
{
|
|
432
|
+
value: 90,
|
|
433
|
+
pointer: { color: '#dc2626' }
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
animateToValue: true
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Reading values
|
|
442
|
+
|
|
443
|
+
```js
|
|
444
|
+
const requested = gauge.getValue();
|
|
445
|
+
const displayed = gauge.getDisplayedValue();
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
- `getValue()` returns the stable requested target.
|
|
449
|
+
- `getDisplayedValue()` returns the current animated base value.
|
|
450
|
+
- Neither method returns the jittered pointer-only visual value.
|
|
451
|
+
|
|
452
|
+
## Lifecycle
|
|
453
|
+
|
|
454
|
+
```js
|
|
455
|
+
gauge.pause();
|
|
456
|
+
gauge.resume();
|
|
457
|
+
gauge.refresh();
|
|
458
|
+
gauge.destroy();
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
- `pause()` stops active animation and jitter work.
|
|
462
|
+
- `resume()` continues pending visual work without counting paused time.
|
|
463
|
+
- `refresh()` rebuilds the gauge from current options. Use it after relevant fonts,
|
|
464
|
+
CSS variables, or external plugin state changes.
|
|
465
|
+
- `destroy()` releases animation frames, observers, listeners, controller resources,
|
|
466
|
+
and generated content.
|
|
467
|
+
|
|
468
|
+
Do not call instance methods after `destroy()`.
|
|
469
|
+
|
|
470
|
+
## Overflow behavior
|
|
471
|
+
|
|
472
|
+
```js
|
|
473
|
+
overflow: 'clamp'
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
- `clamp`: incoming values are constrained to `min ... max`.
|
|
477
|
+
- `expand`: an incoming out-of-range value expands the configured range and re-renders.
|
|
478
|
+
- `allow`: stores and renders out-of-range values without changing the configured
|
|
479
|
+
range; display clamping is disabled.
|
|
480
|
+
|
|
481
|
+
Use `clamp` unless the application explicitly needs one of the other contracts.
|
|
482
|
+
|
|
483
|
+
## Inversion
|
|
484
|
+
|
|
485
|
+
```js
|
|
486
|
+
invert: true
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Inversion reverses visual direction without changing the numeric definitions. Zones
|
|
490
|
+
remain attached to their numeric values. Do not reverse zone arrays or swap their
|
|
491
|
+
numeric endpoints to implement inversion.
|
|
492
|
+
|
|
493
|
+
## Responsive layout
|
|
494
|
+
|
|
495
|
+
Default layout is intrinsic and responsive:
|
|
496
|
+
|
|
497
|
+
```js
|
|
498
|
+
layout: {
|
|
499
|
+
mode: 'intrinsic',
|
|
500
|
+
padding: 8,
|
|
501
|
+
includeGeometry: true
|
|
502
|
+
},
|
|
503
|
+
responsive: true
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
The host should have an available width. Gaugeit.js applies responsive SVG sizing and
|
|
507
|
+
calculates a content-aware viewBox.
|
|
508
|
+
|
|
509
|
+
For a fixed-size card area, constrain the host with CSS and use contain mode:
|
|
510
|
+
|
|
511
|
+
```css
|
|
512
|
+
.dashboard-gauge {
|
|
513
|
+
width: 320px;
|
|
514
|
+
height: 220px;
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
```js
|
|
519
|
+
Gaugeit.createGauge('.dashboard-gauge', {
|
|
520
|
+
type: 'heritage-round',
|
|
521
|
+
layout: {
|
|
522
|
+
mode: 'contain'
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Do not set only a zero or collapsed parent size and expect SVG to invent page layout.
|
|
528
|
+
|
|
529
|
+
## Readouts
|
|
530
|
+
|
|
531
|
+
The readout group is enabled by default, but individual parts are not all visible:
|
|
532
|
+
|
|
533
|
+
```js
|
|
534
|
+
readout: {
|
|
535
|
+
visible: true,
|
|
536
|
+
title: {
|
|
537
|
+
visible: true,
|
|
538
|
+
text: 'PRESSURE'
|
|
539
|
+
},
|
|
540
|
+
value: {
|
|
541
|
+
visible: true,
|
|
542
|
+
fractionDigits: 1
|
|
543
|
+
},
|
|
544
|
+
unit: {
|
|
545
|
+
visible: true,
|
|
546
|
+
text: 'bar'
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
`readout.visible: true` alone does not enable `title`, `value`, or `unit`.
|
|
552
|
+
|
|
553
|
+
## Lights
|
|
554
|
+
|
|
555
|
+
Every built-in type includes the shared light layer. A partial object inherits shared
|
|
556
|
+
defaults:
|
|
557
|
+
|
|
558
|
+
```js
|
|
559
|
+
light: {
|
|
560
|
+
visible: true
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
A threshold light:
|
|
565
|
+
|
|
566
|
+
```js
|
|
567
|
+
light: {
|
|
568
|
+
visible: true,
|
|
569
|
+
color: 'red',
|
|
570
|
+
trigger: {
|
|
571
|
+
mode: 'above',
|
|
572
|
+
source: 'target',
|
|
573
|
+
value: 85
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Trigger sources:
|
|
579
|
+
|
|
580
|
+
- `target`: stable requested value; default and usually best for warnings;
|
|
581
|
+
- `displayed`: current animated base value;
|
|
582
|
+
- `pointer`: animated value plus visual jitter.
|
|
583
|
+
|
|
584
|
+
Effect interaction:
|
|
585
|
+
|
|
586
|
+
- blink takes precedence over pulse;
|
|
587
|
+
- flicker may overlay blink or pulse;
|
|
588
|
+
- pulse and flicker run only while the light is active;
|
|
589
|
+
- reduced-motion preference suppresses the motion effects;
|
|
590
|
+
- `glowRadius` is independent from lamp `radius` in 0.1.1;
|
|
591
|
+
- `glowOpacity` is independent from illuminated-lens `opacity`.
|
|
592
|
+
|
|
593
|
+
For a deterministic steady light, disable all motion:
|
|
594
|
+
|
|
595
|
+
```js
|
|
596
|
+
light: {
|
|
597
|
+
visible: true,
|
|
598
|
+
glow: true,
|
|
599
|
+
pulse: false,
|
|
600
|
+
blink: false,
|
|
601
|
+
flicker: false,
|
|
602
|
+
trigger: {
|
|
603
|
+
mode: 'manual'
|
|
604
|
+
},
|
|
605
|
+
on: true
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
## Data-attribute mounting
|
|
610
|
+
|
|
611
|
+
```html
|
|
612
|
+
<div
|
|
613
|
+
data-gaugeit
|
|
614
|
+
data-gaugeit-type="arc"
|
|
615
|
+
data-gaugeit-min="0"
|
|
616
|
+
data-gaugeit-max="100"
|
|
617
|
+
data-gaugeit-value="63"
|
|
618
|
+
data-gaugeit-options='{"animation":{"startFrom":"value"}}'
|
|
619
|
+
></div>
|
|
620
|
+
|
|
621
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
622
|
+
<script>
|
|
623
|
+
Gaugeit.autoMount();
|
|
624
|
+
</script>
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Supported dataset fields are translated into normal options. The JSON in
|
|
628
|
+
`data-gaugeit-options` must be valid JSON, not JavaScript object syntax. Auto-mounting
|
|
629
|
+
is duplicate-safe.
|
|
630
|
+
|
|
631
|
+
A custom selector and root are supported:
|
|
632
|
+
|
|
633
|
+
```js
|
|
634
|
+
Gaugeit.autoMount('[data-dashboard-gauge]', document.querySelector('#dashboard'));
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
Managed-instance helpers:
|
|
638
|
+
|
|
639
|
+
```js
|
|
640
|
+
const host = document.querySelector('#gauge');
|
|
641
|
+
const instance = Gaugeit.getMountedGauge(host);
|
|
642
|
+
const existed = Gaugeit.unmountGauge(host);
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
## Custom element
|
|
646
|
+
|
|
647
|
+
```html
|
|
648
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
649
|
+
<script>
|
|
650
|
+
Gaugeit.defineGaugeitElement();
|
|
651
|
+
</script>
|
|
652
|
+
|
|
653
|
+
<gauge-it
|
|
654
|
+
type="classic"
|
|
655
|
+
min="0"
|
|
656
|
+
max="240"
|
|
657
|
+
value="88"
|
|
658
|
+
options='{"readout":{"title":{"visible":true,"text":"VOLTAGE"}}}'
|
|
659
|
+
></gauge-it>
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Observed attributes are `value`, `min`, `max`, `type`, and `options`. Attribute changes
|
|
663
|
+
replace the complete declarative option object. The element destroys its instance when
|
|
664
|
+
disconnected.
|
|
665
|
+
|
|
666
|
+
With npm:
|
|
667
|
+
|
|
668
|
+
```js
|
|
669
|
+
import { defineGaugeitElement } from 'gaugeit.js/web-component';
|
|
670
|
+
import 'gaugeit.js/css';
|
|
671
|
+
|
|
672
|
+
defineGaugeitElement();
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
## React
|
|
676
|
+
|
|
677
|
+
```jsx
|
|
678
|
+
import { useMemo, useState } from 'react';
|
|
679
|
+
import Gaugeit from 'gaugeit.js/react';
|
|
680
|
+
import 'gaugeit.js/css';
|
|
681
|
+
|
|
682
|
+
export default function LoadGauge() {
|
|
683
|
+
const [value, setValue] = useState(62);
|
|
684
|
+
|
|
685
|
+
const options = useMemo(() => ({
|
|
686
|
+
type: 'arc',
|
|
687
|
+
min: 0,
|
|
688
|
+
max: 100,
|
|
689
|
+
zones: [
|
|
690
|
+
{ min: 0, max: 60, color: '#22c55e' },
|
|
691
|
+
{ min: 60, max: 85, color: '#facc15' },
|
|
692
|
+
{ min: 85, max: 100, color: '#ef4444' }
|
|
693
|
+
]
|
|
694
|
+
}), []);
|
|
695
|
+
|
|
696
|
+
return (
|
|
697
|
+
<Gaugeit
|
|
698
|
+
value={value}
|
|
699
|
+
options={options}
|
|
700
|
+
className="load-gauge"
|
|
701
|
+
/>
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
React owns the host element. Gaugeit.js owns only the generated content inside it. The
|
|
707
|
+
adapter:
|
|
708
|
+
|
|
709
|
+
- creates an instance on mount;
|
|
710
|
+
- replaces the complete options object when `options` identity changes;
|
|
711
|
+
- keeps explicit `value` authoritative;
|
|
712
|
+
- forwards a ref to the live `Gauge` instance;
|
|
713
|
+
- destroys the instance on unmount.
|
|
714
|
+
|
|
715
|
+
Do not import the source adapter by repository-relative path from an installed package.
|
|
716
|
+
Use `gaugeit.js/react`.
|
|
717
|
+
|
|
718
|
+
## Laravel Blade
|
|
719
|
+
|
|
720
|
+
After Composer asset installation, a Blade page may include:
|
|
721
|
+
|
|
722
|
+
```blade
|
|
723
|
+
@push('styles')
|
|
724
|
+
<link
|
|
725
|
+
rel="stylesheet"
|
|
726
|
+
href="{{ asset('assets/vendor/gaugeit/gaugeit.min.css') }}"
|
|
727
|
+
>
|
|
728
|
+
@endpush
|
|
729
|
+
|
|
730
|
+
<div
|
|
731
|
+
id="pressure-gauge"
|
|
732
|
+
data-gauge-options='@json([
|
|
733
|
+
"type" => "classic",
|
|
734
|
+
"min" => 0,
|
|
735
|
+
"max" => 10,
|
|
736
|
+
"value" => $pressure,
|
|
737
|
+
])'
|
|
738
|
+
></div>
|
|
739
|
+
|
|
740
|
+
@push('scripts')
|
|
741
|
+
<script src="{{ asset('assets/vendor/gaugeit/gaugeit.umd.min.js') }}"></script>
|
|
742
|
+
<script>
|
|
743
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
744
|
+
const host = document.querySelector('#pressure-gauge');
|
|
745
|
+
const options = JSON.parse(host.dataset.gaugeOptions);
|
|
746
|
+
Gaugeit.createGauge(host, options);
|
|
747
|
+
});
|
|
748
|
+
</script>
|
|
749
|
+
@endpush
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
The PHP layer emits data. Browser JavaScript creates the SVG gauge.
|
|
753
|
+
|
|
754
|
+
## Twig
|
|
755
|
+
|
|
756
|
+
```twig
|
|
757
|
+
<link
|
|
758
|
+
rel="stylesheet"
|
|
759
|
+
href="{{ asset('assets/vendor/gaugeit/gaugeit.min.css') }}"
|
|
760
|
+
>
|
|
761
|
+
|
|
762
|
+
<div
|
|
763
|
+
id="temperature-gauge"
|
|
764
|
+
data-options="{{ {
|
|
765
|
+
type: 'classic',
|
|
766
|
+
min: -20,
|
|
767
|
+
max: 120,
|
|
768
|
+
value: temperature
|
|
769
|
+
}|json_encode|e('html_attr') }}"
|
|
770
|
+
></div>
|
|
771
|
+
|
|
772
|
+
<script src="{{ asset('assets/vendor/gaugeit/gaugeit.umd.min.js') }}"></script>
|
|
773
|
+
<script>
|
|
774
|
+
const host = document.querySelector('#temperature-gauge');
|
|
775
|
+
Gaugeit.createGauge(host, JSON.parse(host.dataset.options));
|
|
776
|
+
</script>
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
Escape JSON for an HTML attribute. Do not place unescaped application data directly
|
|
780
|
+
inside executable JavaScript.
|
|
781
|
+
|
|
782
|
+
## Plain PHP
|
|
783
|
+
|
|
784
|
+
```php
|
|
785
|
+
<?php
|
|
786
|
+
$options = [
|
|
787
|
+
'type' => 'arc',
|
|
788
|
+
'min' => 0,
|
|
789
|
+
'max' => 100,
|
|
790
|
+
'value' => $value,
|
|
791
|
+
];
|
|
792
|
+
?>
|
|
793
|
+
|
|
794
|
+
<link rel="stylesheet" href="/assets/vendor/gaugeit/gaugeit.min.css">
|
|
795
|
+
|
|
796
|
+
<div
|
|
797
|
+
id="status-gauge"
|
|
798
|
+
data-options="<?= htmlspecialchars(
|
|
799
|
+
json_encode($options, JSON_THROW_ON_ERROR),
|
|
800
|
+
ENT_QUOTES,
|
|
801
|
+
'UTF-8'
|
|
802
|
+
) ?>"
|
|
803
|
+
></div>
|
|
804
|
+
|
|
805
|
+
<script src="/assets/vendor/gaugeit/gaugeit.umd.min.js"></script>
|
|
806
|
+
<script>
|
|
807
|
+
const host = document.querySelector('#status-gauge');
|
|
808
|
+
Gaugeit.createGauge(host, JSON.parse(host.dataset.options));
|
|
809
|
+
</script>
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
## DOM events
|
|
813
|
+
|
|
814
|
+
Events are dispatched from the host element:
|
|
815
|
+
|
|
816
|
+
```js
|
|
817
|
+
const host = document.querySelector('#gauge');
|
|
818
|
+
|
|
819
|
+
host.addEventListener('gaugeit:change', (event) => {
|
|
820
|
+
console.log(event.detail.value);
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
host.addEventListener('gaugeit:settled', (event) => {
|
|
824
|
+
console.log(event.detail.displayedValue);
|
|
825
|
+
});
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
Available event names:
|
|
829
|
+
|
|
830
|
+
```text
|
|
831
|
+
gaugeit:render
|
|
832
|
+
gaugeit:change
|
|
833
|
+
gaugeit:displaychange
|
|
834
|
+
gaugeit:animationstart
|
|
835
|
+
gaugeit:animationend
|
|
836
|
+
gaugeit:settled
|
|
837
|
+
gaugeit:typechange
|
|
838
|
+
gaugeit:pause
|
|
839
|
+
gaugeit:resume
|
|
840
|
+
gaugeit:destroy
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
Use namespaced events instead of global callbacks.
|
|
844
|
+
|
|
845
|
+
## Custom types
|
|
846
|
+
|
|
847
|
+
Register a type before creating instances that use it:
|
|
848
|
+
|
|
849
|
+
```js
|
|
850
|
+
Gaugeit.registerGaugeType('compact-status', {
|
|
851
|
+
description: 'Compact dashboard status gauge.',
|
|
852
|
+
defaults: {
|
|
853
|
+
geometry: {
|
|
854
|
+
width: 260,
|
|
855
|
+
height: 150,
|
|
856
|
+
centerX: 130,
|
|
857
|
+
centerY: 132,
|
|
858
|
+
startAngle: 200,
|
|
859
|
+
endAngle: 340
|
|
860
|
+
},
|
|
861
|
+
pointer: {
|
|
862
|
+
type: 'arrow',
|
|
863
|
+
length: 75,
|
|
864
|
+
width: 6
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
layers: [
|
|
868
|
+
Gaugeit.layers.face,
|
|
869
|
+
Gaugeit.layers.zones,
|
|
870
|
+
Gaugeit.layers.ticks,
|
|
871
|
+
Gaugeit.layers.labels,
|
|
872
|
+
Gaugeit.layers.light,
|
|
873
|
+
Gaugeit.layers.readout,
|
|
874
|
+
Gaugeit.layers.pointer
|
|
875
|
+
]
|
|
876
|
+
});
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
Layer order is paint order. In the example, the pointer is last so it paints above the
|
|
880
|
+
readout.
|
|
881
|
+
|
|
882
|
+
## Accessibility
|
|
883
|
+
|
|
884
|
+
Always provide a meaningful label for production gauges:
|
|
885
|
+
|
|
886
|
+
```js
|
|
887
|
+
accessibility: {
|
|
888
|
+
label: 'Battery charge',
|
|
889
|
+
description: 'Remaining charge in the main battery.',
|
|
890
|
+
valueText: (value) => `${Math.round(value)} percent`
|
|
891
|
+
}
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
The generated root uses `role="meter"` and updates `aria-valuemin`,
|
|
895
|
+
`aria-valuemax`, `aria-valuenow`, and optional `aria-valuetext`.
|
|
896
|
+
|
|
897
|
+
A visually decorative gauge still needs an intentional accessibility decision. Do not
|
|
898
|
+
silently remove meter semantics from the library.
|
|
899
|
+
|
|
900
|
+
## Browser support assumptions
|
|
901
|
+
|
|
902
|
+
Gaugeit.js uses modern browser APIs and targets ES2018-compatible runtime behavior.
|
|
903
|
+
When generating code for an older environment, do not mutate Gaugeit.js source
|
|
904
|
+
arbitrarily. Add an application-level transpilation or compatibility plan.
|
|
905
|
+
|
|
906
|
+
The optional `test:chromium` development command expects a globally available
|
|
907
|
+
`chromium` executable. That requirement does not apply to normal library consumers.
|
|
908
|
+
|
|
909
|
+
---
|
|
910
|
+
|
|
911
|
+
# Part II — Complete option reference
|
|
912
|
+
|
|
913
|
+
This document describes the public option model in Gaugeit.js 0.1.1. Use exact key
|
|
914
|
+
names and nesting. Do not invent aliases unless this document explicitly lists one.
|
|
915
|
+
|
|
916
|
+
Runtime option resolution order is:
|
|
917
|
+
|
|
918
|
+
1. shared base defaults;
|
|
919
|
+
2. selected type defaults;
|
|
920
|
+
3. instance options;
|
|
921
|
+
4. optional type `configure()` hook;
|
|
922
|
+
5. validation and normalization.
|
|
923
|
+
|
|
924
|
+
Plain objects merge recursively. Arrays are replaced. A selected type may override many
|
|
925
|
+
shared defaults, so the values below are the **base defaults**, not necessarily the
|
|
926
|
+
final resolved values for every built-in type.
|
|
927
|
+
|
|
928
|
+
## Complete base-default shape
|
|
929
|
+
|
|
930
|
+
```js
|
|
931
|
+
{
|
|
932
|
+
type: 'arc',
|
|
933
|
+
min: 0,
|
|
934
|
+
max: 100,
|
|
935
|
+
value: 0,
|
|
936
|
+
invert: false,
|
|
937
|
+
overflow: 'clamp',
|
|
938
|
+
precision: 3,
|
|
939
|
+
className: '',
|
|
940
|
+
|
|
941
|
+
layout: {
|
|
942
|
+
mode: 'intrinsic',
|
|
943
|
+
padding: 8,
|
|
944
|
+
includeGeometry: true
|
|
945
|
+
},
|
|
946
|
+
|
|
947
|
+
geometry: {
|
|
948
|
+
mode: 'radial',
|
|
949
|
+
width: 320,
|
|
950
|
+
height: 220,
|
|
951
|
+
centerX: 160,
|
|
952
|
+
centerY: 174,
|
|
953
|
+
radius: 120,
|
|
954
|
+
startAngle: 180,
|
|
955
|
+
endAngle: 360,
|
|
956
|
+
preserveAspectRatio: 'xMidYMid meet',
|
|
957
|
+
aspectRatio: null
|
|
958
|
+
},
|
|
959
|
+
|
|
960
|
+
linear: {
|
|
961
|
+
originY: 140,
|
|
962
|
+
startX: 36,
|
|
963
|
+
startY: 0,
|
|
964
|
+
endX: 284,
|
|
965
|
+
endY: 0,
|
|
966
|
+
tickSide: 'negative',
|
|
967
|
+
labelOffset: 34,
|
|
968
|
+
zoneOffset: -12
|
|
969
|
+
},
|
|
970
|
+
|
|
971
|
+
face: {
|
|
972
|
+
visible: false,
|
|
973
|
+
shape: 'panel',
|
|
974
|
+
fit: 'geometry',
|
|
975
|
+
contentPadding: 8,
|
|
976
|
+
minWidth: null,
|
|
977
|
+
minHeight: null,
|
|
978
|
+
clipContent: false,
|
|
979
|
+
x: 6,
|
|
980
|
+
y: 6,
|
|
981
|
+
width: 308,
|
|
982
|
+
height: 208,
|
|
983
|
+
cornerRadius: 18,
|
|
984
|
+
color: '#ffffff',
|
|
985
|
+
strokeColor: '#d1d5db',
|
|
986
|
+
strokeWidth: 2,
|
|
987
|
+
innerStrokeColor: '#f3f4f6',
|
|
988
|
+
innerStrokeWidth: 1,
|
|
989
|
+
shadow: false,
|
|
990
|
+
glass: false
|
|
991
|
+
},
|
|
992
|
+
|
|
993
|
+
track: {
|
|
994
|
+
visible: true,
|
|
995
|
+
color: '#e5e7eb',
|
|
996
|
+
width: 28,
|
|
997
|
+
radius: 112,
|
|
998
|
+
opacity: 1,
|
|
999
|
+
lineCap: 'butt',
|
|
1000
|
+
showUnderZones: false
|
|
1001
|
+
},
|
|
1002
|
+
|
|
1003
|
+
zones: [],
|
|
1004
|
+
|
|
1005
|
+
scale: {
|
|
1006
|
+
endpointMode: 'auto',
|
|
1007
|
+
radius: 112,
|
|
1008
|
+
position: 'inside',
|
|
1009
|
+
major: {
|
|
1010
|
+
visible: true,
|
|
1011
|
+
interval: null,
|
|
1012
|
+
divisions: 5,
|
|
1013
|
+
length: 13,
|
|
1014
|
+
width: 2,
|
|
1015
|
+
color: '#111827',
|
|
1016
|
+
opacity: 1
|
|
1017
|
+
},
|
|
1018
|
+
minor: {
|
|
1019
|
+
visible: true,
|
|
1020
|
+
subdivisions: 4,
|
|
1021
|
+
length: 7,
|
|
1022
|
+
width: 1,
|
|
1023
|
+
color: '#6b7280',
|
|
1024
|
+
opacity: 0.9
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
|
|
1028
|
+
labels: {
|
|
1029
|
+
visible: true,
|
|
1030
|
+
interval: null,
|
|
1031
|
+
radius: 84,
|
|
1032
|
+
color: '#111827',
|
|
1033
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
1034
|
+
fontSize: 11,
|
|
1035
|
+
fontWeight: 500,
|
|
1036
|
+
fractionDigits: 0,
|
|
1037
|
+
rotation: 'horizontal',
|
|
1038
|
+
formatter: null
|
|
1039
|
+
},
|
|
1040
|
+
|
|
1041
|
+
pointer: {
|
|
1042
|
+
visible: true,
|
|
1043
|
+
type: 'needle',
|
|
1044
|
+
length: 94,
|
|
1045
|
+
shaftLength: null,
|
|
1046
|
+
tailLength: 13,
|
|
1047
|
+
width: 8,
|
|
1048
|
+
color: '#111827',
|
|
1049
|
+
strokeColor: 'none',
|
|
1050
|
+
strokeWidth: 0,
|
|
1051
|
+
opacity: 1,
|
|
1052
|
+
colorByZone: false,
|
|
1053
|
+
cap: {
|
|
1054
|
+
visible: true,
|
|
1055
|
+
stackOrder: 'above',
|
|
1056
|
+
radius: 8,
|
|
1057
|
+
color: '#111827',
|
|
1058
|
+
strokeColor: '#ffffff',
|
|
1059
|
+
strokeWidth: 2
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
|
|
1063
|
+
indicator: {
|
|
1064
|
+
visible: false,
|
|
1065
|
+
type: 'hairline',
|
|
1066
|
+
position: 'cross',
|
|
1067
|
+
length: 76,
|
|
1068
|
+
centerOffset: null,
|
|
1069
|
+
offset: 0,
|
|
1070
|
+
width: 2,
|
|
1071
|
+
color: '#b91c1c',
|
|
1072
|
+
strokeColor: 'none',
|
|
1073
|
+
strokeWidth: 0,
|
|
1074
|
+
opacity: 1,
|
|
1075
|
+
lineCap: 'round',
|
|
1076
|
+
colorByZone: false,
|
|
1077
|
+
markerSize: 8,
|
|
1078
|
+
carriageWidth: 14,
|
|
1079
|
+
carriageRadius: 4,
|
|
1080
|
+
cap: {
|
|
1081
|
+
visible: false,
|
|
1082
|
+
stackOrder: 'above',
|
|
1083
|
+
radius: 4,
|
|
1084
|
+
color: '#b91c1c',
|
|
1085
|
+
strokeColor: '#ffffff',
|
|
1086
|
+
strokeWidth: 1
|
|
1087
|
+
}
|
|
1088
|
+
},
|
|
1089
|
+
|
|
1090
|
+
centerZero: {
|
|
1091
|
+
zeroValue: 0,
|
|
1092
|
+
symmetric: false,
|
|
1093
|
+
marker: {
|
|
1094
|
+
visible: false,
|
|
1095
|
+
radius: null,
|
|
1096
|
+
position: 'cross',
|
|
1097
|
+
centerOffset: 0,
|
|
1098
|
+
length: 20,
|
|
1099
|
+
width: 3,
|
|
1100
|
+
color: '#111827',
|
|
1101
|
+
opacity: 1,
|
|
1102
|
+
lineCap: 'round'
|
|
1103
|
+
}
|
|
1104
|
+
},
|
|
1105
|
+
|
|
1106
|
+
tape: {
|
|
1107
|
+
orientation: 'vertical',
|
|
1108
|
+
x: 64,
|
|
1109
|
+
y: 58,
|
|
1110
|
+
width: 192,
|
|
1111
|
+
height: 210,
|
|
1112
|
+
cornerRadius: 12,
|
|
1113
|
+
windowColor: '#e5e7eb',
|
|
1114
|
+
windowStrokeColor: '#475569',
|
|
1115
|
+
windowStrokeWidth: 2,
|
|
1116
|
+
windowInnerStrokeColor: '#d1d5db',
|
|
1117
|
+
windowInnerStrokeWidth: 1,
|
|
1118
|
+
stripColor: '#ffffff',
|
|
1119
|
+
textColor: '#111827',
|
|
1120
|
+
tickColor: '#334155',
|
|
1121
|
+
minorTickColor: '#64748b',
|
|
1122
|
+
minorTickOpacity: 0.85,
|
|
1123
|
+
majorTickWidth: 2,
|
|
1124
|
+
minorTickWidth: 1,
|
|
1125
|
+
majorTickLength: 24,
|
|
1126
|
+
minorTickLength: 12,
|
|
1127
|
+
tickInset: 64,
|
|
1128
|
+
labelOffset: 10,
|
|
1129
|
+
interval: null,
|
|
1130
|
+
minorSubdivisions: 4,
|
|
1131
|
+
majorSpacing: 52,
|
|
1132
|
+
indicatorColor: '#b91c1c',
|
|
1133
|
+
indicatorWidth: 2.5,
|
|
1134
|
+
fadeSize: 32,
|
|
1135
|
+
fadeColor: '#ffffff',
|
|
1136
|
+
colorByZone: false
|
|
1137
|
+
},
|
|
1138
|
+
|
|
1139
|
+
light: {
|
|
1140
|
+
visible: false,
|
|
1141
|
+
x: 93,
|
|
1142
|
+
y: 110,
|
|
1143
|
+
radius: 8,
|
|
1144
|
+
glowRadius: 20,
|
|
1145
|
+
glowSharpness: 0,
|
|
1146
|
+
color: 'red',
|
|
1147
|
+
customColor: '',
|
|
1148
|
+
offColor: '#f3ead6',
|
|
1149
|
+
offEdgeColor: '#817768',
|
|
1150
|
+
bezelColor: '#4b5563',
|
|
1151
|
+
bezelHighlightColor: '#f8fafc',
|
|
1152
|
+
opacity: 1,
|
|
1153
|
+
glowOpacity: 0.15,
|
|
1154
|
+
on: false,
|
|
1155
|
+
glow: true,
|
|
1156
|
+
pulse: false,
|
|
1157
|
+
pulseInterval: 2600,
|
|
1158
|
+
blink: false,
|
|
1159
|
+
blinkInterval: 1000,
|
|
1160
|
+
flicker: true,
|
|
1161
|
+
flickerIntensity: 0.1,
|
|
1162
|
+
flickerGlowRadius: 0.1,
|
|
1163
|
+
flickerMinInterval: 45,
|
|
1164
|
+
flickerMaxInterval: 140,
|
|
1165
|
+
trigger: {
|
|
1166
|
+
mode: 'below',
|
|
1167
|
+
source: 'target',
|
|
1168
|
+
value: 15,
|
|
1169
|
+
min: 0,
|
|
1170
|
+
max: 15
|
|
1171
|
+
}
|
|
1172
|
+
},
|
|
1173
|
+
|
|
1174
|
+
readout: {
|
|
1175
|
+
visible: true,
|
|
1176
|
+
title: {
|
|
1177
|
+
visible: false,
|
|
1178
|
+
text: '',
|
|
1179
|
+
x: 'auto',
|
|
1180
|
+
y: 'auto',
|
|
1181
|
+
position: 'top',
|
|
1182
|
+
margin: 12,
|
|
1183
|
+
offsetX: 0,
|
|
1184
|
+
offsetY: 0,
|
|
1185
|
+
color: '#374151',
|
|
1186
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
1187
|
+
fontSize: 14,
|
|
1188
|
+
fontWeight: 700,
|
|
1189
|
+
letterSpacing: 0.8
|
|
1190
|
+
},
|
|
1191
|
+
value: {
|
|
1192
|
+
visible: false,
|
|
1193
|
+
x: 'auto',
|
|
1194
|
+
y: 54,
|
|
1195
|
+
position: 'center',
|
|
1196
|
+
margin: 12,
|
|
1197
|
+
offsetX: 0,
|
|
1198
|
+
offsetY: 0,
|
|
1199
|
+
color: '#111827',
|
|
1200
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
1201
|
+
fontSize: 38,
|
|
1202
|
+
fontWeight: 750,
|
|
1203
|
+
fractionDigits: 0,
|
|
1204
|
+
prefix: '',
|
|
1205
|
+
suffix: '',
|
|
1206
|
+
locale: undefined,
|
|
1207
|
+
formatter: null
|
|
1208
|
+
},
|
|
1209
|
+
unit: {
|
|
1210
|
+
visible: false,
|
|
1211
|
+
text: '',
|
|
1212
|
+
x: 'auto',
|
|
1213
|
+
y: 76,
|
|
1214
|
+
position: 'bottom',
|
|
1215
|
+
margin: 12,
|
|
1216
|
+
offsetX: 0,
|
|
1217
|
+
offsetY: 0,
|
|
1218
|
+
color: '#6b7280',
|
|
1219
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
1220
|
+
fontSize: 12,
|
|
1221
|
+
fontWeight: 600
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
|
|
1225
|
+
animation: {
|
|
1226
|
+
enabled: true,
|
|
1227
|
+
duration: 850,
|
|
1228
|
+
easing: 'easeOutCubic',
|
|
1229
|
+
animateOnInit: true,
|
|
1230
|
+
startFrom: 'min',
|
|
1231
|
+
respectReducedMotion: true
|
|
1232
|
+
},
|
|
1233
|
+
|
|
1234
|
+
effects: {
|
|
1235
|
+
jitter: {
|
|
1236
|
+
enabled: false,
|
|
1237
|
+
amplitude: 0.45,
|
|
1238
|
+
amplitudeUnit: 'value',
|
|
1239
|
+
frequency: 4,
|
|
1240
|
+
smoothing: 0.86,
|
|
1241
|
+
whenIdleOnly: true
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1244
|
+
|
|
1245
|
+
accessibility: {
|
|
1246
|
+
label: 'Gauge',
|
|
1247
|
+
description: '',
|
|
1248
|
+
valueText: null
|
|
1249
|
+
},
|
|
1250
|
+
|
|
1251
|
+
performance: {
|
|
1252
|
+
pauseWhenHidden: true,
|
|
1253
|
+
pauseWhenOffscreen: true
|
|
1254
|
+
},
|
|
1255
|
+
|
|
1256
|
+
responsive: true
|
|
1257
|
+
}
|
|
1258
|
+
```
|
|
1259
|
+
|
|
1260
|
+
## Top-level options
|
|
1261
|
+
|
|
1262
|
+
| Key | Type | Base default | Allowed or normalized behavior | Layout impact |
|
|
1263
|
+
| --- | --- | --- | --- | --- |
|
|
1264
|
+
| `type` | string | `arc` | must be a registered type name | selects all type defaults and layers |
|
|
1265
|
+
| `min` | number | `0` | non-finite becomes `0` | scale, zones, labels, movement |
|
|
1266
|
+
| `max` | number | `100` | forced to greater than `min` | scale, zones, labels, movement |
|
|
1267
|
+
| `value` | number | `0` | normalized according to `overflow` | dynamic only unless range expands |
|
|
1268
|
+
| `invert` | boolean | `false` | only literal `true` enables | reverses visual mapping |
|
|
1269
|
+
| `overflow` | string | `clamp` | `clamp`, `expand`, `allow` | `expand` may rebuild range |
|
|
1270
|
+
| `precision` | integer | `3` | clamped to `0 ... 12` | display/event rounding |
|
|
1271
|
+
| `className` | string | empty | extra generated-root class | CSS only |
|
|
1272
|
+
| `responsive` | boolean | `true` | false disables width-100% sizing | host sizing |
|
|
1273
|
+
| `layout` | object | see below | restored when malformed | viewBox and host size |
|
|
1274
|
+
| `geometry` | object | see below | restored and normalized | primary composition |
|
|
1275
|
+
| `linear` | object | see below | used by linear family | primary composition |
|
|
1276
|
+
| `face` | object | see below | shared static layer | bounds and clipping |
|
|
1277
|
+
| `track` | object | see below | radial and linear tracks | bounds |
|
|
1278
|
+
| `zones` | array | `[]` | array replaced; entries normalized | bounds and colors |
|
|
1279
|
+
| `scale` | object | see below | major/minor scale | bounds |
|
|
1280
|
+
| `labels` | object | see below | static text labels | bounds |
|
|
1281
|
+
| `pointer` | object | see below | radial moving visual | bounds and dynamic update |
|
|
1282
|
+
| `indicator` | object | see below | linear moving visual | bounds and dynamic update |
|
|
1283
|
+
| `centerZero` | object | see below | fixed zero reference | range and bounds |
|
|
1284
|
+
| `tape` | object | see below | rolling tape family | bounds and dynamic update |
|
|
1285
|
+
| `light` | object | see below | shared annunciator | bounds, timers, dynamic state |
|
|
1286
|
+
| `readout` | object | see below | title/value/unit | bounds and dynamic text |
|
|
1287
|
+
| `animation` | object | see below | base-value transition | runtime state |
|
|
1288
|
+
| `effects.jitter` | object | see below | visual-only movement | moving artwork only |
|
|
1289
|
+
| `accessibility` | object | see below | meter semantics | no visual layout |
|
|
1290
|
+
| `performance` | object | see below | lifecycle observers | no visual layout |
|
|
1291
|
+
|
|
1292
|
+
### `type`
|
|
1293
|
+
|
|
1294
|
+
Example:
|
|
1295
|
+
|
|
1296
|
+
```js
|
|
1297
|
+
{ type: 'heritage-round' }
|
|
1298
|
+
```
|
|
1299
|
+
|
|
1300
|
+
Use one exact built-in name or a previously registered custom type. An unknown type
|
|
1301
|
+
causes registry lookup to fail. Do not silently substitute a made-up name.
|
|
1302
|
+
|
|
1303
|
+
### `min`, `max`, and `value`
|
|
1304
|
+
|
|
1305
|
+
Example:
|
|
1306
|
+
|
|
1307
|
+
```js
|
|
1308
|
+
{
|
|
1309
|
+
min: -50,
|
|
1310
|
+
max: 50,
|
|
1311
|
+
value: 12
|
|
1312
|
+
}
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
If `max <= min`, runtime normalization sets `max = min + 1`.
|
|
1316
|
+
|
|
1317
|
+
### `overflow`
|
|
1318
|
+
|
|
1319
|
+
```js
|
|
1320
|
+
{ overflow: 'clamp' }
|
|
1321
|
+
```
|
|
1322
|
+
|
|
1323
|
+
- `clamp`: constrain incoming values to the range.
|
|
1324
|
+
- `expand`: extend `min` or `max` when an incoming value exceeds the range, then
|
|
1325
|
+
re-resolve options and render.
|
|
1326
|
+
- `allow`: preserve the configured range but allow the dynamic value outside it.
|
|
1327
|
+
|
|
1328
|
+
Common misuse: using `allow` when the visual should stay within the scale. Prefer
|
|
1329
|
+
`clamp` for meters that represent bounded values.
|
|
1330
|
+
|
|
1331
|
+
### `precision`
|
|
1332
|
+
|
|
1333
|
+
This is the internal displayed-value rounding precision, not the same as label or
|
|
1334
|
+
readout fraction digits.
|
|
1335
|
+
|
|
1336
|
+
## `layout`
|
|
1337
|
+
|
|
1338
|
+
| Key | Type | Base default | Normalization | Layout impact |
|
|
1339
|
+
| --- | --- | --- | --- | --- |
|
|
1340
|
+
| `mode` | `'intrinsic' \| 'contain'` | `intrinsic` | invalid becomes `intrinsic` | determines host sizing mode |
|
|
1341
|
+
| `padding` | number | `8` | minimum `0` | expands final viewBox |
|
|
1342
|
+
| `includeGeometry` | boolean | `true` | false only when explicitly false | keeps logical dimensions as minimum bounds |
|
|
1343
|
+
|
|
1344
|
+
Example:
|
|
1345
|
+
|
|
1346
|
+
```js
|
|
1347
|
+
layout: {
|
|
1348
|
+
mode: 'contain',
|
|
1349
|
+
padding: 12,
|
|
1350
|
+
includeGeometry: true
|
|
1351
|
+
}
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
`intrinsic` calculates content-aware bounds and gives the SVG a natural aspect ratio.
|
|
1355
|
+
`contain` uses the same viewBox inside a host whose width and height are controlled by
|
|
1356
|
+
CSS.
|
|
1357
|
+
|
|
1358
|
+
Common misuse: selecting `contain` without giving the host a usable CSS height.
|
|
1359
|
+
|
|
1360
|
+
## `geometry`
|
|
1361
|
+
|
|
1362
|
+
| Key | Type | Base default | Normalization | Used by |
|
|
1363
|
+
| --- | --- | --- | --- | --- |
|
|
1364
|
+
| `mode` | `'radial' \| 'linear' \| 'tape'` | `radial` | invalid becomes `radial` | type family |
|
|
1365
|
+
| `width` | number | `320` | minimum `40` | all families and intrinsic sizing |
|
|
1366
|
+
| `height` | number | `220` | minimum `40` | all families and intrinsic sizing |
|
|
1367
|
+
| `centerX` | number | `160` | fallback `width / 2` | radial |
|
|
1368
|
+
| `centerY` | number | `174` | fallback `height * 0.78` | radial |
|
|
1369
|
+
| `radius` | number | `120` | minimum `1` | radial and custom layers |
|
|
1370
|
+
| `startAngle` | number | `180` | finite; zero sweep repaired | radial |
|
|
1371
|
+
| `endAngle` | number | `360` | sweep limited to 360 degrees | radial |
|
|
1372
|
+
| `preserveAspectRatio` | string | `xMidYMid meet` | converted to string | SVG |
|
|
1373
|
+
| `aspectRatio` | number or `null` | `null` | positive or null | resizable presets |
|
|
1374
|
+
|
|
1375
|
+
Example semicircle:
|
|
1376
|
+
|
|
1377
|
+
```js
|
|
1378
|
+
geometry: {
|
|
1379
|
+
startAngle: 180,
|
|
1380
|
+
endAngle: 360
|
|
1381
|
+
}
|
|
1382
|
+
```
|
|
1383
|
+
|
|
1384
|
+
Example using the public helper:
|
|
1385
|
+
|
|
1386
|
+
```js
|
|
1387
|
+
const angles = Gaugeit.geometry.centeredArcAngles(220, 15);
|
|
1388
|
+
|
|
1389
|
+
const options = {
|
|
1390
|
+
geometry: angles
|
|
1391
|
+
};
|
|
1392
|
+
```
|
|
1393
|
+
|
|
1394
|
+
`centerAngle: 0` means the normal upright orientation, corresponding to SVG angle 270.
|
|
1395
|
+
Positive center angles rotate clockwise.
|
|
1396
|
+
|
|
1397
|
+
Common misuse: manually setting `geometry.mode` to a family inconsistent with the
|
|
1398
|
+
selected built-in type. Prefer selecting the correct type and overriding only necessary
|
|
1399
|
+
coordinates.
|
|
1400
|
+
|
|
1401
|
+
## `linear`
|
|
1402
|
+
|
|
1403
|
+
Public linear Y coordinates are Cartesian around `originY`: positive values move
|
|
1404
|
+
upward even though internal SVG Y increases downward.
|
|
1405
|
+
|
|
1406
|
+
| Key | Type | Base default | Meaning |
|
|
1407
|
+
| --- | --- | --- | --- |
|
|
1408
|
+
| `originY` | number | `140` | SVG baseline representing public Y zero |
|
|
1409
|
+
| `startX` | number | `36` | minimum-side X before inversion |
|
|
1410
|
+
| `startY` | number | `0` | minimum-side positive-up Y |
|
|
1411
|
+
| `endX` | number | `284` | maximum-side X before inversion |
|
|
1412
|
+
| `endY` | number | `0` | maximum-side positive-up Y |
|
|
1413
|
+
| `tickSide` | `'negative' \| 'positive' \| 'cross'` | `negative` | side of axis normal |
|
|
1414
|
+
| `labelOffset` | number | `34` | positive moves visually upward on left-to-right axis |
|
|
1415
|
+
| `zoneOffset` | number | `-12` | positive moves visually upward on left-to-right axis |
|
|
1416
|
+
|
|
1417
|
+
A zero-length axis is repaired to one logical unit.
|
|
1418
|
+
|
|
1419
|
+
Example vertical axis:
|
|
1420
|
+
|
|
1421
|
+
```js
|
|
1422
|
+
linear: {
|
|
1423
|
+
originY: 270,
|
|
1424
|
+
startX: 90,
|
|
1425
|
+
startY: 0,
|
|
1426
|
+
endX: 90,
|
|
1427
|
+
endY: 220,
|
|
1428
|
+
tickSide: 'positive'
|
|
1429
|
+
}
|
|
1430
|
+
```
|
|
1431
|
+
|
|
1432
|
+
Common misuse: applying raw SVG downward-positive Y assumptions to these public fields.
|
|
1433
|
+
|
|
1434
|
+
## `face`
|
|
1435
|
+
|
|
1436
|
+
| Key | Type | Base default | Notes |
|
|
1437
|
+
| --- | --- | --- | --- |
|
|
1438
|
+
| `visible` | boolean | `false` | type presets may enable |
|
|
1439
|
+
| `shape` | `'panel' \| 'circle'` | `panel` | invalid becomes panel |
|
|
1440
|
+
| `fit` | `'geometry' \| 'content'` | `geometry` | content fit follows rendered content |
|
|
1441
|
+
| `contentPadding` | number | `8` | minimum zero |
|
|
1442
|
+
| `minWidth` | number or `null` | `null` | minimum one when present |
|
|
1443
|
+
| `minHeight` | number or `null` | `null` | minimum one when present |
|
|
1444
|
+
| `clipContent` | boolean | `false` | clips later non-face layers to inner bezel |
|
|
1445
|
+
| `x`, `y` | number | `6`, `6` | face origin |
|
|
1446
|
+
| `width`, `height` | number | `308`, `208` | minimum one |
|
|
1447
|
+
| `cornerRadius` | number | `18` | minimum zero |
|
|
1448
|
+
| `color` | CSS color | `#ffffff` | face fill |
|
|
1449
|
+
| `strokeColor` | CSS color | `#d1d5db` | outer stroke |
|
|
1450
|
+
| `strokeWidth` | number | `2` | minimum zero |
|
|
1451
|
+
| `innerStrokeColor` | CSS color | `#f3f4f6` | inner bezel |
|
|
1452
|
+
| `innerStrokeWidth` | number | `1` | minimum zero |
|
|
1453
|
+
| `shadow` | boolean | `false` | reserves extra bounds |
|
|
1454
|
+
| `glass` | boolean | `false` | visual highlight |
|
|
1455
|
+
|
|
1456
|
+
Example:
|
|
1457
|
+
|
|
1458
|
+
```js
|
|
1459
|
+
face: {
|
|
1460
|
+
visible: true,
|
|
1461
|
+
fit: 'content',
|
|
1462
|
+
contentPadding: 12,
|
|
1463
|
+
color: '#fff8e7',
|
|
1464
|
+
glass: true
|
|
1465
|
+
}
|
|
1466
|
+
```
|
|
1467
|
+
|
|
1468
|
+
Interaction: when `clipContent` is true and the face layer is first, the renderer
|
|
1469
|
+
applies the clip path to later layers. New custom layers should use `renderer.group()`
|
|
1470
|
+
to inherit this behavior.
|
|
1471
|
+
|
|
1472
|
+
## `track`
|
|
1473
|
+
|
|
1474
|
+
| Key | Type | Base default | Notes |
|
|
1475
|
+
| --- | --- | --- | --- |
|
|
1476
|
+
| `visible` | boolean | `true` | base scale track |
|
|
1477
|
+
| `color` | CSS color | `#e5e7eb` | track color |
|
|
1478
|
+
| `width` | number | `28` | zone default-width fallback |
|
|
1479
|
+
| `radius` | number | `112` | radial track radius |
|
|
1480
|
+
| `opacity` | number | `1` | visual opacity |
|
|
1481
|
+
| `lineCap` | string | `butt` | SVG stroke-linecap |
|
|
1482
|
+
| `showUnderZones` | boolean | `false` | controls transparent-zone gaps |
|
|
1483
|
+
|
|
1484
|
+
When `showUnderZones` is false, a transparent zone is a true gap. When true, the track
|
|
1485
|
+
remains visible under every zone.
|
|
1486
|
+
|
|
1487
|
+
## `zones`
|
|
1488
|
+
|
|
1489
|
+
Type: array of zone objects. The entire array is replaced by option updates.
|
|
1490
|
+
|
|
1491
|
+
```js
|
|
1492
|
+
zones: [
|
|
1493
|
+
{
|
|
1494
|
+
min: 0,
|
|
1495
|
+
max: 25,
|
|
1496
|
+
color: '#ef4444',
|
|
1497
|
+
width: 20,
|
|
1498
|
+
taper: {
|
|
1499
|
+
startWidth: 0,
|
|
1500
|
+
endWidth: 32,
|
|
1501
|
+
segments: 48
|
|
1502
|
+
},
|
|
1503
|
+
radius: 112,
|
|
1504
|
+
opacity: 1,
|
|
1505
|
+
lineCap: 'butt',
|
|
1506
|
+
className: 'warning-zone',
|
|
1507
|
+
offset: 0
|
|
1508
|
+
}
|
|
1509
|
+
]
|
|
1510
|
+
```
|
|
1511
|
+
|
|
1512
|
+
| Key | Type | Default | Applies to |
|
|
1513
|
+
| --- | --- | --- | --- |
|
|
1514
|
+
| `min` | number | required | all |
|
|
1515
|
+
| `max` | number | required | all |
|
|
1516
|
+
| `color` | CSS color | transparent when missing | all |
|
|
1517
|
+
| `strokeStyle` | CSS color | alias for `color` | compatibility |
|
|
1518
|
+
| `width` | number | track width fallback | radial and linear |
|
|
1519
|
+
| `taper.startWidth` | number | zone width fallback | radial |
|
|
1520
|
+
| `taper.endWidth` | number | zone width fallback | radial |
|
|
1521
|
+
| `taper.segments` | integer | `48` | radial, clamped `2 ... 256` |
|
|
1522
|
+
| `radius` | number | track/type fallback | radial |
|
|
1523
|
+
| `opacity` | number | `1` | all |
|
|
1524
|
+
| `lineCap` | string | type/track fallback | uniform radial and linear |
|
|
1525
|
+
| `className` | string | empty | generated zone element |
|
|
1526
|
+
| `offset` | number | linear zone offset fallback | linear |
|
|
1527
|
+
|
|
1528
|
+
Zones are clamped to `min ... max`; invalid or empty intervals are removed. Later
|
|
1529
|
+
overlapping zones win. `pointer.colorByZone`, `indicator.colorByZone`, and
|
|
1530
|
+
`tape.colorByZone` use the last matching zone.
|
|
1531
|
+
|
|
1532
|
+
A missing color, `transparent`, or `none` produces a gap when the track is not shown
|
|
1533
|
+
under zones.
|
|
1534
|
+
|
|
1535
|
+
## `scale`
|
|
1536
|
+
|
|
1537
|
+
Shared fields:
|
|
1538
|
+
|
|
1539
|
+
| Key | Type | Base default | Notes |
|
|
1540
|
+
| --- | --- | --- | --- |
|
|
1541
|
+
| `endpointMode` | `'auto' \| 'both' \| 'dedupe'` | `auto` | auto deduplicates closed-scale maximum |
|
|
1542
|
+
| `radius` | number | `112` | minimum one |
|
|
1543
|
+
| `position` | `'inside' \| 'outside' \| 'cross'` | `inside` | tick placement |
|
|
1544
|
+
|
|
1545
|
+
### `scale.major`
|
|
1546
|
+
|
|
1547
|
+
| Key | Type | Base default | Notes |
|
|
1548
|
+
| --- | --- | --- | --- |
|
|
1549
|
+
| `visible` | boolean | `true` | type preset may change |
|
|
1550
|
+
| `interval` | number or `null` | `null` | positive explicit spacing |
|
|
1551
|
+
| `divisions` | integer | `5` | desired divisions if no interval |
|
|
1552
|
+
| `length` | number | `13` | minimum zero |
|
|
1553
|
+
| `width` | number | `2` | minimum zero |
|
|
1554
|
+
| `color` | CSS color | `#111827` | stroke |
|
|
1555
|
+
| `opacity` | number | `1` | stroke opacity |
|
|
1556
|
+
|
|
1557
|
+
### `scale.minor`
|
|
1558
|
+
|
|
1559
|
+
| Key | Type | Base default | Notes |
|
|
1560
|
+
| --- | --- | --- | --- |
|
|
1561
|
+
| `visible` | boolean | `true` | type preset may change |
|
|
1562
|
+
| `subdivisions` | integer | `4` | count between adjacent major ticks |
|
|
1563
|
+
| `length` | number | `7` | minimum zero |
|
|
1564
|
+
| `width` | number | `1` | minimum zero |
|
|
1565
|
+
| `color` | CSS color | `#6b7280` | stroke |
|
|
1566
|
+
| `opacity` | number | `0.9` | stroke opacity |
|
|
1567
|
+
|
|
1568
|
+
`major.interval` takes precedence over `major.divisions`.
|
|
1569
|
+
|
|
1570
|
+
## `labels`
|
|
1571
|
+
|
|
1572
|
+
| Key | Type | Base default | Notes |
|
|
1573
|
+
| --- | --- | --- | --- |
|
|
1574
|
+
| `visible` | boolean | `true` | static scale labels |
|
|
1575
|
+
| `interval` | number or `null` | `null` | falls back to major interval |
|
|
1576
|
+
| `radius` | number | `84` | radial placement |
|
|
1577
|
+
| `color` | CSS color | `#111827` | text color |
|
|
1578
|
+
| `fontFamily` | string | system stack | SVG text |
|
|
1579
|
+
| `fontSize` | number | `11` | minimum one |
|
|
1580
|
+
| `fontWeight` | string or number | `500` | SVG text |
|
|
1581
|
+
| `fractionDigits` | integer | `0` | clamped `0 ... 12` |
|
|
1582
|
+
| `rotation` | `'horizontal' \| 'radial' \| 'tangent'` | `horizontal` | radial labels |
|
|
1583
|
+
| `formatter` | function or `null` | `null` | `(value, context) => string` |
|
|
1584
|
+
|
|
1585
|
+
Formatter context:
|
|
1586
|
+
|
|
1587
|
+
```js
|
|
1588
|
+
{
|
|
1589
|
+
min,
|
|
1590
|
+
max,
|
|
1591
|
+
options
|
|
1592
|
+
}
|
|
1593
|
+
```
|
|
1594
|
+
|
|
1595
|
+
Common misuse: returning non-string application objects. Coerce to readable text.
|
|
1596
|
+
|
|
1597
|
+
## `pointer`
|
|
1598
|
+
|
|
1599
|
+
Radial moving visual:
|
|
1600
|
+
|
|
1601
|
+
| Key | Type | Base default | Notes |
|
|
1602
|
+
| --- | --- | --- | --- |
|
|
1603
|
+
| `visible` | boolean | `true` | radial layer |
|
|
1604
|
+
| `type` | `'needle' \| 'line' \| 'arrow' \| 'spear'` | `needle` | invalid becomes needle |
|
|
1605
|
+
| `length` | number | `94` | minimum zero |
|
|
1606
|
+
| `shaftLength` | number or `null` | `null` | clamped to pointer length |
|
|
1607
|
+
| `tailLength` | number | `13` | minimum zero |
|
|
1608
|
+
| `width` | number | `8` | minimum `0.5` |
|
|
1609
|
+
| `color` | CSS color | `#111827` | fill/stroke by type |
|
|
1610
|
+
| `strokeColor` | CSS color | `none` | outline |
|
|
1611
|
+
| `strokeWidth` | number | `0` | minimum zero |
|
|
1612
|
+
| `opacity` | number | `1` | visual opacity |
|
|
1613
|
+
| `colorByZone` | boolean | `false` | use active zone color |
|
|
1614
|
+
| `cap.visible` | boolean | `true` | center cap |
|
|
1615
|
+
| `cap.stackOrder` | `'above' \| 'below'` | `above` | SVG paint relation |
|
|
1616
|
+
| `cap.radius` | number | `8` | minimum zero |
|
|
1617
|
+
| `cap.color` | CSS color | `#111827` | fill |
|
|
1618
|
+
| `cap.strokeColor` | CSS color | `#ffffff` | outline |
|
|
1619
|
+
| `cap.strokeWidth` | number | `2` | minimum zero |
|
|
1620
|
+
|
|
1621
|
+
Layer order, not CSS z-index, controls whether the entire pointer paints over readout
|
|
1622
|
+
text. `cap.stackOrder` only controls cap-versus-pointer order inside the pointer layer.
|
|
1623
|
+
|
|
1624
|
+
## `indicator`
|
|
1625
|
+
|
|
1626
|
+
Linear moving visual:
|
|
1627
|
+
|
|
1628
|
+
| Key | Type | Base default | Notes |
|
|
1629
|
+
| --- | --- | --- | --- |
|
|
1630
|
+
| `visible` | boolean | `false` | linear presets enable |
|
|
1631
|
+
| `type` | `'hairline' \| 'marker' \| 'carriage'` | `hairline` | indicator shape |
|
|
1632
|
+
| `position` | `'negative' \| 'positive' \| 'cross'` | `cross` | normal span |
|
|
1633
|
+
| `length` | number | `76` | minimum zero |
|
|
1634
|
+
| `centerOffset` | number | `null` in base | positive-up public offset |
|
|
1635
|
+
| `offset` | number | `0` | deprecated compatibility alias |
|
|
1636
|
+
| `width` | number | `2` | minimum `0.5` |
|
|
1637
|
+
| `color` | CSS color | `#b91c1c` | fill/stroke |
|
|
1638
|
+
| `strokeColor` | CSS color | `none` | outline |
|
|
1639
|
+
| `strokeWidth` | number | `0` | minimum zero |
|
|
1640
|
+
| `opacity` | number | `1` | visual opacity |
|
|
1641
|
+
| `lineCap` | string | `round` | SVG linecap |
|
|
1642
|
+
| `colorByZone` | boolean | `false` | use active zone color |
|
|
1643
|
+
| `markerSize` | number | `8` | marker type |
|
|
1644
|
+
| `carriageWidth` | number | `14` | carriage type |
|
|
1645
|
+
| `carriageRadius` | number | `4` | carriage corners |
|
|
1646
|
+
| `cap.visible` | boolean | `false` | optional cap |
|
|
1647
|
+
| `cap.stackOrder` | `'above' \| 'below'` | `above` | cap relation |
|
|
1648
|
+
| `cap.radius` | number | `4` | minimum zero |
|
|
1649
|
+
| `cap.color` | CSS color | `#b91c1c` | fill |
|
|
1650
|
+
| `cap.strokeColor` | CSS color | `#ffffff` | outline |
|
|
1651
|
+
| `cap.strokeWidth` | number | `1` | minimum zero |
|
|
1652
|
+
|
|
1653
|
+
Use `centerOffset` in new code. Positive values move upward for a left-to-right axis.
|
|
1654
|
+
The older `offset` retains its original raw axis-normal sign.
|
|
1655
|
+
|
|
1656
|
+
## `centerZero`
|
|
1657
|
+
|
|
1658
|
+
| Key | Type | Base default | Notes |
|
|
1659
|
+
| --- | --- | --- | --- |
|
|
1660
|
+
| `zeroValue` | number | `0` | numeric center reference |
|
|
1661
|
+
| `symmetric` | boolean | `false` | expands range equally around zeroValue |
|
|
1662
|
+
| `marker.visible` | boolean | `false` | preset may enable |
|
|
1663
|
+
| `marker.radius` | number or `null` | `null` | radial marker radius |
|
|
1664
|
+
| `marker.position` | `'negative' \| 'positive' \| 'cross'` | `cross` | linear marker span |
|
|
1665
|
+
| `marker.centerOffset` | number | `0` | positive-up on horizontal axis |
|
|
1666
|
+
| `marker.length` | number | `20` | minimum zero |
|
|
1667
|
+
| `marker.width` | number | `3` | minimum `0.5` |
|
|
1668
|
+
| `marker.color` | CSS color | `#111827` | stroke |
|
|
1669
|
+
| `marker.opacity` | number | `1` | clamped `0 ... 1` |
|
|
1670
|
+
| `marker.lineCap` | string | `round` | SVG linecap |
|
|
1671
|
+
|
|
1672
|
+
For center-zero types, use a range that contains `zeroValue`. With
|
|
1673
|
+
`symmetric: true`, the runtime expands the larger side across the reference:
|
|
1674
|
+
|
|
1675
|
+
```js
|
|
1676
|
+
{
|
|
1677
|
+
min: -30,
|
|
1678
|
+
max: 80,
|
|
1679
|
+
centerZero: {
|
|
1680
|
+
zeroValue: 0,
|
|
1681
|
+
symmetric: true
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
```
|
|
1685
|
+
|
|
1686
|
+
resolves to a symmetric `-80 ... 80` range.
|
|
1687
|
+
|
|
1688
|
+
## `tape`
|
|
1689
|
+
|
|
1690
|
+
Used by `rolling-tape` and custom tape types:
|
|
1691
|
+
|
|
1692
|
+
| Key | Type | Base default | Notes |
|
|
1693
|
+
| --- | --- | --- | --- |
|
|
1694
|
+
| `orientation` | `'vertical' \| 'horizontal'` | `vertical` | travel axis |
|
|
1695
|
+
| `x`, `y` | number | `64`, `58` | window origin |
|
|
1696
|
+
| `width`, `height` | number | `192`, `210` | minimum 20 |
|
|
1697
|
+
| `cornerRadius` | number | `12` | minimum zero |
|
|
1698
|
+
| `windowColor` | CSS color | `#e5e7eb` | outer window |
|
|
1699
|
+
| `windowStrokeColor` | CSS color | `#475569` | border |
|
|
1700
|
+
| `windowStrokeWidth` | number | `2` | minimum zero |
|
|
1701
|
+
| `windowInnerStrokeColor` | CSS color | `#d1d5db` | inner border |
|
|
1702
|
+
| `windowInnerStrokeWidth` | number | `1` | minimum zero |
|
|
1703
|
+
| `stripColor` | CSS color | `#ffffff` | moving strip |
|
|
1704
|
+
| `textColor` | CSS color | `#111827` | numeric labels |
|
|
1705
|
+
| `tickColor` | CSS color | `#334155` | major ticks |
|
|
1706
|
+
| `minorTickColor` | CSS color | `#64748b` | minor ticks |
|
|
1707
|
+
| `minorTickOpacity` | number | `0.85` | clamped `0 ... 1` |
|
|
1708
|
+
| `majorTickWidth` | number | `2` | minimum `0.5` |
|
|
1709
|
+
| `minorTickWidth` | number | `1` | minimum `0.5` |
|
|
1710
|
+
| `majorTickLength` | number | `24` | minimum zero |
|
|
1711
|
+
| `minorTickLength` | number | `12` | minimum zero |
|
|
1712
|
+
| `tickInset` | number | `64` | placement from edge |
|
|
1713
|
+
| `labelOffset` | number | `10` | text placement |
|
|
1714
|
+
| `interval` | number or `null` | `null` | positive explicit major interval |
|
|
1715
|
+
| `minorSubdivisions` | integer | `4` | count between majors |
|
|
1716
|
+
| `majorSpacing` | number | `52` | minimum two |
|
|
1717
|
+
| `indicatorColor` | CSS color | `#b91c1c` | fixed reference line |
|
|
1718
|
+
| `indicatorWidth` | number | `2.5` | minimum `0.5` |
|
|
1719
|
+
| `fadeSize` | number | `32` | clamped to half travel-axis size |
|
|
1720
|
+
| `fadeColor` | CSS color | `#ffffff` | edge fade |
|
|
1721
|
+
| `colorByZone` | boolean | `false` | slot/reference color behavior |
|
|
1722
|
+
|
|
1723
|
+
The scale moves behind a fixed edge-to-edge indicator. Do not animate a separate
|
|
1724
|
+
pointer for this type. Slot reuse keeps DOM size independent of the full numeric range.
|
|
1725
|
+
|
|
1726
|
+
## `light`
|
|
1727
|
+
|
|
1728
|
+
The shared light is available to every built-in type. A custom type must include
|
|
1729
|
+
`Gaugeit.layers.light`.
|
|
1730
|
+
|
|
1731
|
+
| Key | Type | Base default | Normalization and interaction |
|
|
1732
|
+
| --- | --- | --- | --- |
|
|
1733
|
+
| `visible` | boolean | `false` | only true renders |
|
|
1734
|
+
| `x`, `y` | number | `93`, `110` | linear Y uses positive-up convention |
|
|
1735
|
+
| `radius` | number | `8` | minimum one; physical bulb |
|
|
1736
|
+
| `glowRadius` | number or `null` | `20` | minimum zero; null derives `radius * 1.82` |
|
|
1737
|
+
| `glowSharpness` | number | `0` | clamped `0 ... 1`; 1 removes blur |
|
|
1738
|
+
| `color` | named palette | `red` | ten allowed names |
|
|
1739
|
+
| `customColor` | CSS color string | empty | overrides active palette when non-empty |
|
|
1740
|
+
| `offColor` | CSS color | `#f3ead6` | unlit lens center |
|
|
1741
|
+
| `offEdgeColor` | CSS color | `#817768` | unlit lens edge |
|
|
1742
|
+
| `bezelColor` | CSS color | `#4b5563` | hardware |
|
|
1743
|
+
| `bezelHighlightColor` | CSS color | `#f8fafc` | hardware highlight |
|
|
1744
|
+
| `opacity` | number | `1` | illuminated-lens opacity only |
|
|
1745
|
+
| `glowOpacity` | number | `0.15` | halo opacity only |
|
|
1746
|
+
| `on` | boolean | `false` | manual trigger state |
|
|
1747
|
+
| `glow` | boolean | `true` | false hides halo |
|
|
1748
|
+
| `pulse` | boolean | `false` | active steady-light breathing |
|
|
1749
|
+
| `pulseInterval` | number | `2600` | minimum 400 ms |
|
|
1750
|
+
| `blink` | boolean | `false` | active blink; takes precedence over pulse |
|
|
1751
|
+
| `blinkInterval` | number | `1000` | minimum 100 ms |
|
|
1752
|
+
| `flicker` | boolean | `true` | randomized overlay while active |
|
|
1753
|
+
| `flickerIntensity` | number | `0.1` | clamped `0 ... 1` |
|
|
1754
|
+
| `flickerGlowRadius` | number | `0.1` | relative ± variation, clamped `0 ... 1` |
|
|
1755
|
+
| `flickerMinInterval` | number | `45` | minimum 16 ms |
|
|
1756
|
+
| `flickerMaxInterval` | number | `140` | minimum 16 ms; order repaired |
|
|
1757
|
+
| `trigger.mode` | enum | `below` | `manual`, `below`, `above`, `between` |
|
|
1758
|
+
| `trigger.source` | enum | `target` | `target`, `displayed`, `pointer` |
|
|
1759
|
+
| `trigger.value` | number | `15` | inclusive below/above threshold |
|
|
1760
|
+
| `trigger.min` | number | `0` | inclusive between lower bound |
|
|
1761
|
+
| `trigger.max` | number | `15` | inclusive between upper bound |
|
|
1762
|
+
|
|
1763
|
+
Allowed palettes:
|
|
1764
|
+
|
|
1765
|
+
```text
|
|
1766
|
+
red
|
|
1767
|
+
green
|
|
1768
|
+
blue
|
|
1769
|
+
amber
|
|
1770
|
+
yellow
|
|
1771
|
+
orange
|
|
1772
|
+
white
|
|
1773
|
+
halogen
|
|
1774
|
+
cyan
|
|
1775
|
+
brown
|
|
1776
|
+
```
|
|
1777
|
+
|
|
1778
|
+
Examples:
|
|
1779
|
+
|
|
1780
|
+
```js
|
|
1781
|
+
light: {
|
|
1782
|
+
visible: true,
|
|
1783
|
+
color: 'amber',
|
|
1784
|
+
glowRadius: 28,
|
|
1785
|
+
glowSharpness: 0.25,
|
|
1786
|
+
glowOpacity: 0.22,
|
|
1787
|
+
trigger: {
|
|
1788
|
+
mode: 'above',
|
|
1789
|
+
source: 'target',
|
|
1790
|
+
value: 80
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
```
|
|
1794
|
+
|
|
1795
|
+
```js
|
|
1796
|
+
light: {
|
|
1797
|
+
visible: true,
|
|
1798
|
+
on: true,
|
|
1799
|
+
pulse: true,
|
|
1800
|
+
blink: false,
|
|
1801
|
+
flicker: false,
|
|
1802
|
+
trigger: {
|
|
1803
|
+
mode: 'manual'
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
```
|
|
1807
|
+
|
|
1808
|
+
Important interactions:
|
|
1809
|
+
|
|
1810
|
+
- blink disables pulse while active;
|
|
1811
|
+
- flicker may run with blink or pulse;
|
|
1812
|
+
- flicker and pulse run only while the trigger is active;
|
|
1813
|
+
- reduced motion suppresses blink, pulse, and flicker motion;
|
|
1814
|
+
- `opacity` does not fade the unlit physical lens or bezel;
|
|
1815
|
+
- `glowOpacity` is independent from lens `opacity`;
|
|
1816
|
+
- `glowRadius` controls halo size independently from bulb `radius`;
|
|
1817
|
+
- glow bounds are calculated automatically and must stay synchronized with rendering.
|
|
1818
|
+
|
|
1819
|
+
Common misuse: enabling `on: true` while leaving trigger mode at the default `below`.
|
|
1820
|
+
`on` is read only by `trigger.mode: 'manual'`.
|
|
1821
|
+
|
|
1822
|
+
## `readout`
|
|
1823
|
+
|
|
1824
|
+
`readout.visible` controls the complete group. Each child also needs `visible: true`.
|
|
1825
|
+
|
|
1826
|
+
Shared text-part fields:
|
|
1827
|
+
|
|
1828
|
+
| Key | Type | Base title default | Meaning |
|
|
1829
|
+
| --- | --- | --- | --- |
|
|
1830
|
+
| `visible` | boolean | `false` | only true renders |
|
|
1831
|
+
| `text` | string | empty | title/unit content |
|
|
1832
|
+
| `x` | number or `'auto'` | `auto` | auto centers |
|
|
1833
|
+
| `y` | number or `'auto'` | `auto` for title | auto uses position/margin |
|
|
1834
|
+
| `position` | `'top' \| 'center' \| 'bottom'` | `top` | automatic Y anchor |
|
|
1835
|
+
| `margin` | number | `12` | minimum zero |
|
|
1836
|
+
| `offsetX`, `offsetY` | number | `0` | refine automatic placement only |
|
|
1837
|
+
| `color` | CSS color | type-specific | text fill |
|
|
1838
|
+
| `fontFamily` | string | system stack | SVG text |
|
|
1839
|
+
| `fontSize` | number | `14` title | minimum one |
|
|
1840
|
+
| `fontWeight` | string or number | `700` title | SVG text |
|
|
1841
|
+
| `letterSpacing` | number | `0.8` title | SVG text |
|
|
1842
|
+
|
|
1843
|
+
`readout.value` additionally supports:
|
|
1844
|
+
|
|
1845
|
+
| Key | Type | Base default |
|
|
1846
|
+
| --- | --- | --- |
|
|
1847
|
+
| `fractionDigits` | integer | `0`, clamped `0 ... 12` |
|
|
1848
|
+
| `prefix` | string | empty |
|
|
1849
|
+
| `suffix` | string | empty |
|
|
1850
|
+
| `locale` | string or string array | undefined |
|
|
1851
|
+
| `formatter` | function or null | null |
|
|
1852
|
+
|
|
1853
|
+
A value formatter takes precedence over locale and fraction settings.
|
|
1854
|
+
|
|
1855
|
+
Numeric coordinates are final SVG coordinates. Automatic offsets apply only when the
|
|
1856
|
+
corresponding coordinate is `'auto'`.
|
|
1857
|
+
|
|
1858
|
+
Example:
|
|
1859
|
+
|
|
1860
|
+
```js
|
|
1861
|
+
readout: {
|
|
1862
|
+
visible: true,
|
|
1863
|
+
title: {
|
|
1864
|
+
visible: true,
|
|
1865
|
+
text: 'TEMPERATURE'
|
|
1866
|
+
},
|
|
1867
|
+
value: {
|
|
1868
|
+
visible: true,
|
|
1869
|
+
fractionDigits: 1
|
|
1870
|
+
},
|
|
1871
|
+
unit: {
|
|
1872
|
+
visible: true,
|
|
1873
|
+
text: '°C'
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
```
|
|
1877
|
+
|
|
1878
|
+
Common misuse: setting only `readout.visible: true` and expecting the numeric value to
|
|
1879
|
+
appear.
|
|
1880
|
+
|
|
1881
|
+
## `animation`
|
|
1882
|
+
|
|
1883
|
+
| Key | Type | Base default | Notes |
|
|
1884
|
+
| --- | --- | --- | --- |
|
|
1885
|
+
| `enabled` | boolean | `true` | global value animation |
|
|
1886
|
+
| `duration` | number | `850` | milliseconds, minimum zero |
|
|
1887
|
+
| `easing` | string or function | `easeOutCubic` | resolved by easing system |
|
|
1888
|
+
| `animateOnInit` | boolean | `true` | startup animation |
|
|
1889
|
+
| `startFrom` | `'min' \| 'max' \| 'value' \| number` | `min` | invalid becomes min |
|
|
1890
|
+
| `respectReducedMotion` | boolean | `true` | suppresses animation when requested |
|
|
1891
|
+
|
|
1892
|
+
Per-update overrides:
|
|
1893
|
+
|
|
1894
|
+
```js
|
|
1895
|
+
gauge.setValue(80, {
|
|
1896
|
+
animate: true,
|
|
1897
|
+
duration: 1200,
|
|
1898
|
+
easing: 'easeInOutCubic'
|
|
1899
|
+
});
|
|
1900
|
+
```
|
|
1901
|
+
|
|
1902
|
+
## `effects.jitter`
|
|
1903
|
+
|
|
1904
|
+
| Key | Type | Base default | Notes |
|
|
1905
|
+
| --- | --- | --- | --- |
|
|
1906
|
+
| `enabled` | boolean | `false` | visual-only |
|
|
1907
|
+
| `amplitude` | number | `0.45` | minimum zero |
|
|
1908
|
+
| `amplitudeUnit` | `'value' \| 'percent'` | `value` | percent of range |
|
|
1909
|
+
| `frequency` | number | `4` | targets per second, minimum 0.1 |
|
|
1910
|
+
| `smoothing` | number | `0.86` | clamped `0 ... 0.999` |
|
|
1911
|
+
| `whenIdleOnly` | boolean | `true` | suppress during base animation |
|
|
1912
|
+
|
|
1913
|
+
Jitter affects only radial pointer, linear indicator, or rolling tape visual position.
|
|
1914
|
+
It does not affect:
|
|
1915
|
+
|
|
1916
|
+
- `getValue()`;
|
|
1917
|
+
- `getDisplayedValue()`;
|
|
1918
|
+
- readout text;
|
|
1919
|
+
- DOM event values;
|
|
1920
|
+
- ARIA value;
|
|
1921
|
+
- default light trigger source.
|
|
1922
|
+
|
|
1923
|
+
## `accessibility`
|
|
1924
|
+
|
|
1925
|
+
| Key | Type | Base default | Notes |
|
|
1926
|
+
| --- | --- | --- | --- |
|
|
1927
|
+
| `label` | string | `Gauge` | becomes `aria-label` |
|
|
1928
|
+
| `description` | string | empty | rendered in SVG description |
|
|
1929
|
+
| `valueText` | string, function, or null | null | becomes `aria-valuetext` |
|
|
1930
|
+
|
|
1931
|
+
Example:
|
|
1932
|
+
|
|
1933
|
+
```js
|
|
1934
|
+
accessibility: {
|
|
1935
|
+
label: 'Battery charge',
|
|
1936
|
+
description: 'Remaining charge in the main battery.',
|
|
1937
|
+
valueText: (value) => `${Math.round(value)} percent`
|
|
1938
|
+
}
|
|
1939
|
+
```
|
|
1940
|
+
|
|
1941
|
+
The generated root receives `role="meter"`, `aria-valuemin`, `aria-valuemax`, and
|
|
1942
|
+
`aria-valuenow`.
|
|
1943
|
+
|
|
1944
|
+
## `performance`
|
|
1945
|
+
|
|
1946
|
+
| Key | Type | Base default | Notes |
|
|
1947
|
+
| --- | --- | --- | --- |
|
|
1948
|
+
| `pauseWhenHidden` | boolean | `true` | uses page visibility |
|
|
1949
|
+
| `pauseWhenOffscreen` | boolean | `true` | uses IntersectionObserver when available |
|
|
1950
|
+
|
|
1951
|
+
Runtime option replacement reconfigures the observers.
|
|
1952
|
+
|
|
1953
|
+
## Type-specific defaults and AI strategy
|
|
1954
|
+
|
|
1955
|
+
Built-in types intentionally override base defaults. An AI should:
|
|
1956
|
+
|
|
1957
|
+
1. select the closest built-in type;
|
|
1958
|
+
2. provide only application-specific overrides;
|
|
1959
|
+
3. avoid copying all base defaults into every example;
|
|
1960
|
+
4. read the selected type file when exact preset coordinates or colors matter;
|
|
1961
|
+
5. use `replaceOptions()` when switching complete declarative configurations.
|
|
1962
|
+
|
|
1963
|
+
Minimal type-specific usage:
|
|
1964
|
+
|
|
1965
|
+
```js
|
|
1966
|
+
Gaugeit.createGauge('#gauge', {
|
|
1967
|
+
type: 'center-zero',
|
|
1968
|
+
value: -18
|
|
1969
|
+
});
|
|
1970
|
+
```
|
|
1971
|
+
|
|
1972
|
+
is usually safer than reconstructing a center-zero instrument from base defaults.
|
|
1973
|
+
|
|
1974
|
+
## Update semantics reference
|
|
1975
|
+
|
|
1976
|
+
```js
|
|
1977
|
+
gauge.setValue(value, {
|
|
1978
|
+
animate,
|
|
1979
|
+
duration,
|
|
1980
|
+
easing
|
|
1981
|
+
});
|
|
1982
|
+
```
|
|
1983
|
+
|
|
1984
|
+
Dynamic value update; no static re-render unless `overflow: 'expand'` changes range.
|
|
1985
|
+
|
|
1986
|
+
```js
|
|
1987
|
+
gauge.updateOptions(patch, {
|
|
1988
|
+
animateToValue
|
|
1989
|
+
});
|
|
1990
|
+
```
|
|
1991
|
+
|
|
1992
|
+
Recursive patch merge; arrays replaced.
|
|
1993
|
+
|
|
1994
|
+
```js
|
|
1995
|
+
gauge.replaceOptions(options, {
|
|
1996
|
+
animateToValue
|
|
1997
|
+
});
|
|
1998
|
+
```
|
|
1999
|
+
|
|
2000
|
+
Complete declarative replacement; omitted keys return to selected type defaults.
|
|
2001
|
+
|
|
2002
|
+
## Option-generation checklist
|
|
2003
|
+
|
|
2004
|
+
Before generating a Gaugeit.js option object, verify:
|
|
2005
|
+
|
|
2006
|
+
- the type name exists;
|
|
2007
|
+
- `min < max`;
|
|
2008
|
+
- center-zero ranges contain the zero reference;
|
|
2009
|
+
- radial, linear, and tape options match the selected family;
|
|
2010
|
+
- readout child visibility is explicit;
|
|
2011
|
+
- light manual mode is used when `on` controls state;
|
|
2012
|
+
- blink/pulse precedence is intentional;
|
|
2013
|
+
- CDN or package version is pinned where appropriate;
|
|
2014
|
+
- accessibility label is meaningful;
|
|
2015
|
+
- CSS is included unless standalone is used;
|
|
2016
|
+
- high-frequency value changes use `setValue()`;
|
|
2017
|
+
- arrays are intentionally replaced during option updates.
|
|
2018
|
+
|
|
2019
|
+
---
|
|
2020
|
+
|
|
2021
|
+
# Part III — Integration recipes
|
|
2022
|
+
|
|
2023
|
+
These recipes target Gaugeit.js 0.1.1. Each recipe is intended to be copied as a
|
|
2024
|
+
complete starting point. Replace application values and labels, but preserve package
|
|
2025
|
+
names, import paths, global names, lifecycle cleanup, and option nesting.
|
|
2026
|
+
|
|
2027
|
+
## 1. Simple semicircular gauge in plain HTML
|
|
2028
|
+
|
|
2029
|
+
Save as `index.html` beside `dist/`:
|
|
2030
|
+
|
|
2031
|
+
```html
|
|
2032
|
+
<!doctype html>
|
|
2033
|
+
<html lang="en">
|
|
2034
|
+
<head>
|
|
2035
|
+
<meta charset="utf-8">
|
|
2036
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2037
|
+
<title>Gaugeit.js arc gauge</title>
|
|
2038
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2039
|
+
<style>
|
|
2040
|
+
body {
|
|
2041
|
+
max-width: 42rem;
|
|
2042
|
+
margin: 2rem auto;
|
|
2043
|
+
padding: 0 1rem;
|
|
2044
|
+
font-family: system-ui, sans-serif;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
#load-gauge {
|
|
2048
|
+
width: min(100%, 32rem);
|
|
2049
|
+
margin-inline: auto;
|
|
2050
|
+
}
|
|
2051
|
+
</style>
|
|
2052
|
+
</head>
|
|
2053
|
+
<body>
|
|
2054
|
+
<h1>Engine load</h1>
|
|
2055
|
+
<div id="load-gauge"></div>
|
|
2056
|
+
|
|
2057
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2058
|
+
<script>
|
|
2059
|
+
const gauge = Gaugeit.createGauge('#load-gauge', {
|
|
2060
|
+
type: 'arc',
|
|
2061
|
+
min: 0,
|
|
2062
|
+
max: 100,
|
|
2063
|
+
value: 42,
|
|
2064
|
+
zones: [
|
|
2065
|
+
{ min: 0, max: 60, color: '#22c55e' },
|
|
2066
|
+
{ min: 60, max: 85, color: '#facc15' },
|
|
2067
|
+
{ min: 85, max: 100, color: '#ef4444' }
|
|
2068
|
+
],
|
|
2069
|
+
readout: {
|
|
2070
|
+
title: {
|
|
2071
|
+
visible: true,
|
|
2072
|
+
text: 'ENGINE LOAD'
|
|
2073
|
+
},
|
|
2074
|
+
value: {
|
|
2075
|
+
visible: true,
|
|
2076
|
+
suffix: '%'
|
|
2077
|
+
}
|
|
2078
|
+
},
|
|
2079
|
+
accessibility: {
|
|
2080
|
+
label: 'Engine load',
|
|
2081
|
+
valueText: (value) => `${Math.round(value)} percent`
|
|
2082
|
+
}
|
|
2083
|
+
});
|
|
2084
|
+
|
|
2085
|
+
window.setTimeout(() => {
|
|
2086
|
+
gauge.setValue(76);
|
|
2087
|
+
}, 1000);
|
|
2088
|
+
</script>
|
|
2089
|
+
</body>
|
|
2090
|
+
</html>
|
|
2091
|
+
```
|
|
2092
|
+
|
|
2093
|
+
## 2. Classic framed instrument
|
|
2094
|
+
|
|
2095
|
+
```html
|
|
2096
|
+
<!doctype html>
|
|
2097
|
+
<html lang="en">
|
|
2098
|
+
<head>
|
|
2099
|
+
<meta charset="utf-8">
|
|
2100
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2101
|
+
<title>Classic Gaugeit.js instrument</title>
|
|
2102
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2103
|
+
<style>
|
|
2104
|
+
#voltage-gauge {
|
|
2105
|
+
width: min(100%, 34rem);
|
|
2106
|
+
margin: 2rem auto;
|
|
2107
|
+
}
|
|
2108
|
+
</style>
|
|
2109
|
+
</head>
|
|
2110
|
+
<body>
|
|
2111
|
+
<div id="voltage-gauge"></div>
|
|
2112
|
+
|
|
2113
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2114
|
+
<script>
|
|
2115
|
+
const voltageGauge = Gaugeit.createGauge('#voltage-gauge', {
|
|
2116
|
+
type: 'classic',
|
|
2117
|
+
min: 0,
|
|
2118
|
+
max: 240,
|
|
2119
|
+
value: 118,
|
|
2120
|
+
zones: [
|
|
2121
|
+
{ min: 0, max: 90, color: '#ef4444', width: 12 },
|
|
2122
|
+
{ min: 90, max: 150, color: '#22c55e', width: 12 },
|
|
2123
|
+
{ min: 150, max: 240, color: '#ef4444', width: 12 }
|
|
2124
|
+
],
|
|
2125
|
+
pointer: {
|
|
2126
|
+
type: 'spear',
|
|
2127
|
+
colorByZone: true
|
|
2128
|
+
},
|
|
2129
|
+
readout: {
|
|
2130
|
+
title: {
|
|
2131
|
+
visible: true,
|
|
2132
|
+
text: 'VOLTAGE'
|
|
2133
|
+
},
|
|
2134
|
+
value: {
|
|
2135
|
+
visible: true,
|
|
2136
|
+
fractionDigits: 0
|
|
2137
|
+
},
|
|
2138
|
+
unit: {
|
|
2139
|
+
visible: true,
|
|
2140
|
+
text: 'V'
|
|
2141
|
+
}
|
|
2142
|
+
},
|
|
2143
|
+
accessibility: {
|
|
2144
|
+
label: 'Voltage',
|
|
2145
|
+
valueText: (value) => `${Math.round(value)} volts`
|
|
2146
|
+
}
|
|
2147
|
+
});
|
|
2148
|
+
</script>
|
|
2149
|
+
</body>
|
|
2150
|
+
</html>
|
|
2151
|
+
```
|
|
2152
|
+
|
|
2153
|
+
## 3. Radial center-zero balance gauge
|
|
2154
|
+
|
|
2155
|
+
```html
|
|
2156
|
+
<!doctype html>
|
|
2157
|
+
<html lang="en">
|
|
2158
|
+
<head>
|
|
2159
|
+
<meta charset="utf-8">
|
|
2160
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2161
|
+
<title>Center-zero Gaugeit.js gauge</title>
|
|
2162
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2163
|
+
</head>
|
|
2164
|
+
<body>
|
|
2165
|
+
<div id="balance-gauge"></div>
|
|
2166
|
+
|
|
2167
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2168
|
+
<script>
|
|
2169
|
+
const balanceGauge = Gaugeit.createGauge('#balance-gauge', {
|
|
2170
|
+
type: 'center-zero',
|
|
2171
|
+
min: -100,
|
|
2172
|
+
max: 100,
|
|
2173
|
+
value: -18,
|
|
2174
|
+
centerZero: {
|
|
2175
|
+
zeroValue: 0,
|
|
2176
|
+
symmetric: true,
|
|
2177
|
+
marker: {
|
|
2178
|
+
visible: true
|
|
2179
|
+
}
|
|
2180
|
+
},
|
|
2181
|
+
zones: [
|
|
2182
|
+
{ min: -100, max: -60, color: '#ef4444' },
|
|
2183
|
+
{ min: -60, max: 60, color: '#22c55e' },
|
|
2184
|
+
{ min: 60, max: 100, color: '#ef4444' }
|
|
2185
|
+
],
|
|
2186
|
+
readout: {
|
|
2187
|
+
title: {
|
|
2188
|
+
visible: true,
|
|
2189
|
+
text: 'BALANCE'
|
|
2190
|
+
},
|
|
2191
|
+
value: {
|
|
2192
|
+
visible: true,
|
|
2193
|
+
prefix: ''
|
|
2194
|
+
}
|
|
2195
|
+
},
|
|
2196
|
+
accessibility: {
|
|
2197
|
+
label: 'Balance deviation',
|
|
2198
|
+
valueText: (value) => `${Math.round(value)} balance units`
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
</script>
|
|
2202
|
+
</body>
|
|
2203
|
+
</html>
|
|
2204
|
+
```
|
|
2205
|
+
|
|
2206
|
+
The numeric range contains `zeroValue`. `symmetric: true` protects the centered
|
|
2207
|
+
reference if the supplied endpoints are uneven.
|
|
2208
|
+
|
|
2209
|
+
## 4. Classic linear center-zero instrument
|
|
2210
|
+
|
|
2211
|
+
```html
|
|
2212
|
+
<!doctype html>
|
|
2213
|
+
<html lang="en">
|
|
2214
|
+
<head>
|
|
2215
|
+
<meta charset="utf-8">
|
|
2216
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2217
|
+
<title>Classic linear center-zero instrument</title>
|
|
2218
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2219
|
+
<style>
|
|
2220
|
+
#trim-gauge {
|
|
2221
|
+
width: min(100%, 42rem);
|
|
2222
|
+
margin: 2rem auto;
|
|
2223
|
+
}
|
|
2224
|
+
</style>
|
|
2225
|
+
</head>
|
|
2226
|
+
<body>
|
|
2227
|
+
<div id="trim-gauge"></div>
|
|
2228
|
+
|
|
2229
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2230
|
+
<script>
|
|
2231
|
+
const trimGauge = Gaugeit.createGauge('#trim-gauge', {
|
|
2232
|
+
type: 'classic-linear-zero',
|
|
2233
|
+
min: -50,
|
|
2234
|
+
max: 50,
|
|
2235
|
+
value: 12,
|
|
2236
|
+
centerZero: {
|
|
2237
|
+
zeroValue: 0,
|
|
2238
|
+
symmetric: true,
|
|
2239
|
+
marker: {
|
|
2240
|
+
visible: true,
|
|
2241
|
+
color: '#111827'
|
|
2242
|
+
}
|
|
2243
|
+
},
|
|
2244
|
+
indicator: {
|
|
2245
|
+
type: 'hairline',
|
|
2246
|
+
color: '#b91c1c',
|
|
2247
|
+
centerOffset: 0
|
|
2248
|
+
},
|
|
2249
|
+
readout: {
|
|
2250
|
+
title: {
|
|
2251
|
+
visible: true,
|
|
2252
|
+
text: 'TRIM'
|
|
2253
|
+
},
|
|
2254
|
+
value: {
|
|
2255
|
+
visible: true,
|
|
2256
|
+
fractionDigits: 0
|
|
2257
|
+
}
|
|
2258
|
+
},
|
|
2259
|
+
accessibility: {
|
|
2260
|
+
label: 'Trim setting'
|
|
2261
|
+
}
|
|
2262
|
+
});
|
|
2263
|
+
</script>
|
|
2264
|
+
</body>
|
|
2265
|
+
</html>
|
|
2266
|
+
```
|
|
2267
|
+
|
|
2268
|
+
Public linear Y and center offsets are positive-up, not raw SVG coordinates.
|
|
2269
|
+
|
|
2270
|
+
## 5. Rolling tape gauge
|
|
2271
|
+
|
|
2272
|
+
```html
|
|
2273
|
+
<!doctype html>
|
|
2274
|
+
<html lang="en">
|
|
2275
|
+
<head>
|
|
2276
|
+
<meta charset="utf-8">
|
|
2277
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2278
|
+
<title>Gaugeit.js rolling tape</title>
|
|
2279
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2280
|
+
<style>
|
|
2281
|
+
#altitude-tape {
|
|
2282
|
+
width: min(100%, 20rem);
|
|
2283
|
+
margin: 2rem auto;
|
|
2284
|
+
}
|
|
2285
|
+
</style>
|
|
2286
|
+
</head>
|
|
2287
|
+
<body>
|
|
2288
|
+
<div id="altitude-tape"></div>
|
|
2289
|
+
|
|
2290
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2291
|
+
<script>
|
|
2292
|
+
const altitudeTape = Gaugeit.createGauge('#altitude-tape', {
|
|
2293
|
+
type: 'rolling-tape',
|
|
2294
|
+
min: 0,
|
|
2295
|
+
max: 40000,
|
|
2296
|
+
value: 12500,
|
|
2297
|
+
tape: {
|
|
2298
|
+
orientation: 'vertical',
|
|
2299
|
+
interval: 1000,
|
|
2300
|
+
minorSubdivisions: 4,
|
|
2301
|
+
majorSpacing: 54,
|
|
2302
|
+
indicatorColor: '#b91c1c',
|
|
2303
|
+
colorByZone: true
|
|
2304
|
+
},
|
|
2305
|
+
zones: [
|
|
2306
|
+
{ min: 0, max: 10000, color: '#22c55e' },
|
|
2307
|
+
{ min: 10000, max: 25000, color: '#facc15' },
|
|
2308
|
+
{ min: 25000, max: 40000, color: '#ef4444' }
|
|
2309
|
+
],
|
|
2310
|
+
readout: {
|
|
2311
|
+
title: {
|
|
2312
|
+
visible: true,
|
|
2313
|
+
text: 'ALTITUDE'
|
|
2314
|
+
},
|
|
2315
|
+
unit: {
|
|
2316
|
+
visible: true,
|
|
2317
|
+
text: 'ft'
|
|
2318
|
+
}
|
|
2319
|
+
},
|
|
2320
|
+
accessibility: {
|
|
2321
|
+
label: 'Altitude',
|
|
2322
|
+
valueText: (value) => `${Math.round(value)} feet`
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2325
|
+
|
|
2326
|
+
altitudeTape.setValue(13800, {
|
|
2327
|
+
duration: 1400
|
|
2328
|
+
});
|
|
2329
|
+
</script>
|
|
2330
|
+
</body>
|
|
2331
|
+
</html>
|
|
2332
|
+
```
|
|
2333
|
+
|
|
2334
|
+
The tape moves behind its fixed reference line. Do not add a radial pointer to this
|
|
2335
|
+
type.
|
|
2336
|
+
|
|
2337
|
+
## 6. Dynamic value stream
|
|
2338
|
+
|
|
2339
|
+
Use `setValue()` rather than rebuilding options for each sample:
|
|
2340
|
+
|
|
2341
|
+
```html
|
|
2342
|
+
<!doctype html>
|
|
2343
|
+
<html lang="en">
|
|
2344
|
+
<head>
|
|
2345
|
+
<meta charset="utf-8">
|
|
2346
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2347
|
+
<title>Dynamic Gaugeit.js value</title>
|
|
2348
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2349
|
+
</head>
|
|
2350
|
+
<body>
|
|
2351
|
+
<div id="live-gauge"></div>
|
|
2352
|
+
<button id="stop" type="button">Stop updates</button>
|
|
2353
|
+
|
|
2354
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2355
|
+
<script>
|
|
2356
|
+
const gauge = Gaugeit.createGauge('#live-gauge', {
|
|
2357
|
+
type: 'line-scale',
|
|
2358
|
+
min: 0,
|
|
2359
|
+
max: 100,
|
|
2360
|
+
value: 50,
|
|
2361
|
+
animation: {
|
|
2362
|
+
duration: 400
|
|
2363
|
+
},
|
|
2364
|
+
readout: {
|
|
2365
|
+
title: {
|
|
2366
|
+
visible: true,
|
|
2367
|
+
text: 'LIVE VALUE'
|
|
2368
|
+
},
|
|
2369
|
+
value: {
|
|
2370
|
+
visible: true
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
});
|
|
2374
|
+
|
|
2375
|
+
const timer = window.setInterval(() => {
|
|
2376
|
+
const nextValue = Math.round(Math.random() * 100);
|
|
2377
|
+
gauge.setValue(nextValue);
|
|
2378
|
+
}, 1000);
|
|
2379
|
+
|
|
2380
|
+
document.querySelector('#stop').addEventListener('click', () => {
|
|
2381
|
+
window.clearInterval(timer);
|
|
2382
|
+
gauge.destroy();
|
|
2383
|
+
}, { once: true });
|
|
2384
|
+
</script>
|
|
2385
|
+
</body>
|
|
2386
|
+
</html>
|
|
2387
|
+
```
|
|
2388
|
+
|
|
2389
|
+
Application-owned timers remain application-owned and must be cleaned up separately
|
|
2390
|
+
from the gauge.
|
|
2391
|
+
|
|
2392
|
+
## 7. Multiple gauges on one page
|
|
2393
|
+
|
|
2394
|
+
```html
|
|
2395
|
+
<!doctype html>
|
|
2396
|
+
<html lang="en">
|
|
2397
|
+
<head>
|
|
2398
|
+
<meta charset="utf-8">
|
|
2399
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2400
|
+
<title>Multiple Gaugeit.js instruments</title>
|
|
2401
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2402
|
+
<style>
|
|
2403
|
+
.gauge-grid {
|
|
2404
|
+
display: grid;
|
|
2405
|
+
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
|
|
2406
|
+
gap: 1rem;
|
|
2407
|
+
}
|
|
2408
|
+
</style>
|
|
2409
|
+
</head>
|
|
2410
|
+
<body>
|
|
2411
|
+
<div class="gauge-grid">
|
|
2412
|
+
<div id="speed"></div>
|
|
2413
|
+
<div id="temperature"></div>
|
|
2414
|
+
<div id="balance"></div>
|
|
2415
|
+
</div>
|
|
2416
|
+
|
|
2417
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2418
|
+
<script>
|
|
2419
|
+
const gauges = [
|
|
2420
|
+
Gaugeit.createGauge('#speed', {
|
|
2421
|
+
type: 'arc',
|
|
2422
|
+
min: 0,
|
|
2423
|
+
max: 240,
|
|
2424
|
+
value: 88,
|
|
2425
|
+
accessibility: { label: 'Speed' }
|
|
2426
|
+
}),
|
|
2427
|
+
|
|
2428
|
+
Gaugeit.createGauge('#temperature', {
|
|
2429
|
+
type: 'classic',
|
|
2430
|
+
min: -20,
|
|
2431
|
+
max: 120,
|
|
2432
|
+
value: 68,
|
|
2433
|
+
accessibility: { label: 'Temperature' }
|
|
2434
|
+
}),
|
|
2435
|
+
|
|
2436
|
+
Gaugeit.createGauge('#balance', {
|
|
2437
|
+
type: 'center-zero',
|
|
2438
|
+
min: -100,
|
|
2439
|
+
max: 100,
|
|
2440
|
+
value: 6,
|
|
2441
|
+
accessibility: { label: 'Balance' }
|
|
2442
|
+
})
|
|
2443
|
+
];
|
|
2444
|
+
|
|
2445
|
+
window.addEventListener('pagehide', () => {
|
|
2446
|
+
for (const gauge of gauges) {
|
|
2447
|
+
gauge.destroy();
|
|
2448
|
+
}
|
|
2449
|
+
}, { once: true });
|
|
2450
|
+
</script>
|
|
2451
|
+
</body>
|
|
2452
|
+
</html>
|
|
2453
|
+
```
|
|
2454
|
+
|
|
2455
|
+
Each gauge owns one host. Do not reuse one instance across several hosts.
|
|
2456
|
+
|
|
2457
|
+
## 8. Threshold warning light
|
|
2458
|
+
|
|
2459
|
+
```js
|
|
2460
|
+
const gauge = Gaugeit.createGauge('#temperature', {
|
|
2461
|
+
type: 'heritage-round',
|
|
2462
|
+
min: 0,
|
|
2463
|
+
max: 120,
|
|
2464
|
+
value: 72,
|
|
2465
|
+
light: {
|
|
2466
|
+
visible: true,
|
|
2467
|
+
x: 160,
|
|
2468
|
+
y: 88,
|
|
2469
|
+
radius: 8,
|
|
2470
|
+
glowRadius: 24,
|
|
2471
|
+
color: 'red',
|
|
2472
|
+
pulse: false,
|
|
2473
|
+
blink: false,
|
|
2474
|
+
flicker: false,
|
|
2475
|
+
trigger: {
|
|
2476
|
+
mode: 'above',
|
|
2477
|
+
source: 'target',
|
|
2478
|
+
value: 95
|
|
2479
|
+
}
|
|
2480
|
+
},
|
|
2481
|
+
accessibility: {
|
|
2482
|
+
label: 'Coolant temperature'
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
```
|
|
2486
|
+
|
|
2487
|
+
`source: 'target'` makes the warning react to application intent immediately, without
|
|
2488
|
+
waiting for pointer animation.
|
|
2489
|
+
|
|
2490
|
+
## 9. Pulsing active light
|
|
2491
|
+
|
|
2492
|
+
```js
|
|
2493
|
+
const gauge = Gaugeit.createGauge('#status', {
|
|
2494
|
+
type: 'classic',
|
|
2495
|
+
value: 50,
|
|
2496
|
+
light: {
|
|
2497
|
+
visible: true,
|
|
2498
|
+
x: 250,
|
|
2499
|
+
y: 55,
|
|
2500
|
+
radius: 8,
|
|
2501
|
+
glowRadius: 26,
|
|
2502
|
+
glowSharpness: 0.15,
|
|
2503
|
+
glowOpacity: 0.2,
|
|
2504
|
+
color: 'green',
|
|
2505
|
+
on: true,
|
|
2506
|
+
glow: true,
|
|
2507
|
+
pulse: true,
|
|
2508
|
+
pulseInterval: 2600,
|
|
2509
|
+
blink: false,
|
|
2510
|
+
flicker: false,
|
|
2511
|
+
trigger: {
|
|
2512
|
+
mode: 'manual'
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
});
|
|
2516
|
+
```
|
|
2517
|
+
|
|
2518
|
+
The light must be active before pulse is visible. `on` controls the state only in
|
|
2519
|
+
manual trigger mode.
|
|
2520
|
+
|
|
2521
|
+
## 10. Blinking warning light with flicker
|
|
2522
|
+
|
|
2523
|
+
```js
|
|
2524
|
+
const gauge = Gaugeit.createGauge('#pressure', {
|
|
2525
|
+
type: 'classic',
|
|
2526
|
+
min: 0,
|
|
2527
|
+
max: 10,
|
|
2528
|
+
value: 4.2,
|
|
2529
|
+
light: {
|
|
2530
|
+
visible: true,
|
|
2531
|
+
color: 'amber',
|
|
2532
|
+
radius: 8,
|
|
2533
|
+
glowRadius: 22,
|
|
2534
|
+
glowOpacity: 0.18,
|
|
2535
|
+
pulse: true,
|
|
2536
|
+
blink: true,
|
|
2537
|
+
blinkInterval: 900,
|
|
2538
|
+
flicker: true,
|
|
2539
|
+
flickerIntensity: 0.08,
|
|
2540
|
+
flickerGlowRadius: 0.06,
|
|
2541
|
+
flickerMinInterval: 55,
|
|
2542
|
+
flickerMaxInterval: 150,
|
|
2543
|
+
trigger: {
|
|
2544
|
+
mode: 'below',
|
|
2545
|
+
source: 'displayed',
|
|
2546
|
+
value: 2
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
});
|
|
2550
|
+
```
|
|
2551
|
+
|
|
2552
|
+
Blink takes precedence over pulse. Flicker remains an independent overlay. Browsers
|
|
2553
|
+
requesting reduced motion receive a steady accessible state instead of motion.
|
|
2554
|
+
|
|
2555
|
+
## 11. React controlled component
|
|
2556
|
+
|
|
2557
|
+
```jsx
|
|
2558
|
+
import { useMemo, useState } from 'react';
|
|
2559
|
+
import Gaugeit from 'gaugeit.js/react';
|
|
2560
|
+
import 'gaugeit.js/css';
|
|
2561
|
+
|
|
2562
|
+
export default function EngineLoadGauge() {
|
|
2563
|
+
const [value, setValue] = useState(42);
|
|
2564
|
+
|
|
2565
|
+
const options = useMemo(() => ({
|
|
2566
|
+
type: 'arc',
|
|
2567
|
+
min: 0,
|
|
2568
|
+
max: 100,
|
|
2569
|
+
zones: [
|
|
2570
|
+
{ min: 0, max: 60, color: '#22c55e' },
|
|
2571
|
+
{ min: 60, max: 85, color: '#facc15' },
|
|
2572
|
+
{ min: 85, max: 100, color: '#ef4444' }
|
|
2573
|
+
],
|
|
2574
|
+
readout: {
|
|
2575
|
+
title: {
|
|
2576
|
+
visible: true,
|
|
2577
|
+
text: 'ENGINE LOAD'
|
|
2578
|
+
},
|
|
2579
|
+
value: {
|
|
2580
|
+
visible: true,
|
|
2581
|
+
suffix: '%'
|
|
2582
|
+
}
|
|
2583
|
+
},
|
|
2584
|
+
accessibility: {
|
|
2585
|
+
label: 'Engine load',
|
|
2586
|
+
valueText: (current) => `${Math.round(current)} percent`
|
|
2587
|
+
}
|
|
2588
|
+
}), []);
|
|
2589
|
+
|
|
2590
|
+
return (
|
|
2591
|
+
<section>
|
|
2592
|
+
<Gaugeit
|
|
2593
|
+
value={value}
|
|
2594
|
+
options={options}
|
|
2595
|
+
className="engine-load-gauge"
|
|
2596
|
+
/>
|
|
2597
|
+
|
|
2598
|
+
<label>
|
|
2599
|
+
Load
|
|
2600
|
+
<input
|
|
2601
|
+
type="range"
|
|
2602
|
+
min="0"
|
|
2603
|
+
max="100"
|
|
2604
|
+
value={value}
|
|
2605
|
+
onChange={(event) => setValue(Number(event.target.value))}
|
|
2606
|
+
/>
|
|
2607
|
+
</label>
|
|
2608
|
+
</section>
|
|
2609
|
+
);
|
|
2610
|
+
}
|
|
2611
|
+
```
|
|
2612
|
+
|
|
2613
|
+
The explicit `value` prop remains authoritative even if `options` also contains a
|
|
2614
|
+
value. Prefer not to duplicate it.
|
|
2615
|
+
|
|
2616
|
+
## 12. React ref and imperative API
|
|
2617
|
+
|
|
2618
|
+
```jsx
|
|
2619
|
+
import { useMemo, useRef } from 'react';
|
|
2620
|
+
import Gaugeit from 'gaugeit.js/react';
|
|
2621
|
+
import 'gaugeit.js/css';
|
|
2622
|
+
|
|
2623
|
+
export default function ImperativeGauge() {
|
|
2624
|
+
const gaugeRef = useRef(null);
|
|
2625
|
+
|
|
2626
|
+
const options = useMemo(() => ({
|
|
2627
|
+
type: 'classic',
|
|
2628
|
+
min: 0,
|
|
2629
|
+
max: 100,
|
|
2630
|
+
value: 25,
|
|
2631
|
+
accessibility: {
|
|
2632
|
+
label: 'Imperative demonstration gauge'
|
|
2633
|
+
}
|
|
2634
|
+
}), []);
|
|
2635
|
+
|
|
2636
|
+
function setWarningValue() {
|
|
2637
|
+
gaugeRef.current?.setValue(90, {
|
|
2638
|
+
duration: 1200,
|
|
2639
|
+
easing: 'easeInOutCubic'
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
function changeTheme() {
|
|
2644
|
+
gaugeRef.current?.updateOptions({
|
|
2645
|
+
pointer: {
|
|
2646
|
+
color: '#dc2626'
|
|
2647
|
+
},
|
|
2648
|
+
face: {
|
|
2649
|
+
color: '#fff7ed'
|
|
2650
|
+
}
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
return (
|
|
2655
|
+
<div>
|
|
2656
|
+
<Gaugeit
|
|
2657
|
+
ref={gaugeRef}
|
|
2658
|
+
options={options}
|
|
2659
|
+
/>
|
|
2660
|
+
|
|
2661
|
+
<button type="button" onClick={setWarningValue}>
|
|
2662
|
+
Set warning value
|
|
2663
|
+
</button>
|
|
2664
|
+
|
|
2665
|
+
<button type="button" onClick={changeTheme}>
|
|
2666
|
+
Change theme
|
|
2667
|
+
</button>
|
|
2668
|
+
</div>
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2671
|
+
```
|
|
2672
|
+
|
|
2673
|
+
The adapter destroys the live instance on unmount. Do not call `destroy()` manually
|
|
2674
|
+
from ordinary component cleanup unless the component owns a separately created
|
|
2675
|
+
instance outside the adapter.
|
|
2676
|
+
|
|
2677
|
+
## 13. Laravel Composer asset installation
|
|
2678
|
+
|
|
2679
|
+
Install:
|
|
2680
|
+
|
|
2681
|
+
```bash
|
|
2682
|
+
composer require kasperi/gaugeit-js:^0.1
|
|
2683
|
+
vendor/bin/gaugeit-install-assets public/assets/vendor/gaugeit
|
|
2684
|
+
```
|
|
2685
|
+
|
|
2686
|
+
Optional consuming-project automation:
|
|
2687
|
+
|
|
2688
|
+
```json
|
|
2689
|
+
{
|
|
2690
|
+
"scripts": {
|
|
2691
|
+
"assets:gaugeit": "vendor/bin/gaugeit-install-assets public/assets/vendor/gaugeit",
|
|
2692
|
+
"post-install-cmd": [
|
|
2693
|
+
"@assets:gaugeit"
|
|
2694
|
+
],
|
|
2695
|
+
"post-update-cmd": [
|
|
2696
|
+
"@assets:gaugeit"
|
|
2697
|
+
]
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
```
|
|
2701
|
+
|
|
2702
|
+
Blade view:
|
|
2703
|
+
|
|
2704
|
+
```blade
|
|
2705
|
+
@push('styles')
|
|
2706
|
+
<link
|
|
2707
|
+
rel="stylesheet"
|
|
2708
|
+
href="{{ asset('assets/vendor/gaugeit/gaugeit.min.css') }}"
|
|
2709
|
+
>
|
|
2710
|
+
@endpush
|
|
2711
|
+
|
|
2712
|
+
<section>
|
|
2713
|
+
<h2>Prediction accuracy</h2>
|
|
2714
|
+
|
|
2715
|
+
<div
|
|
2716
|
+
id="accuracy-gauge"
|
|
2717
|
+
data-options='@json([
|
|
2718
|
+
"type" => "arc",
|
|
2719
|
+
"min" => 0,
|
|
2720
|
+
"max" => 100,
|
|
2721
|
+
"value" => $accuracy,
|
|
2722
|
+
"readout" => [
|
|
2723
|
+
"value" => [
|
|
2724
|
+
"visible" => true,
|
|
2725
|
+
"suffix" => "%"
|
|
2726
|
+
]
|
|
2727
|
+
],
|
|
2728
|
+
"accessibility" => [
|
|
2729
|
+
"label" => "Prediction accuracy"
|
|
2730
|
+
]
|
|
2731
|
+
])'
|
|
2732
|
+
></div>
|
|
2733
|
+
</section>
|
|
2734
|
+
|
|
2735
|
+
@push('scripts')
|
|
2736
|
+
<script src="{{ asset('assets/vendor/gaugeit/gaugeit.umd.min.js') }}"></script>
|
|
2737
|
+
<script>
|
|
2738
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
2739
|
+
const host = document.querySelector('#accuracy-gauge');
|
|
2740
|
+
if (!host) return;
|
|
2741
|
+
|
|
2742
|
+
const options = JSON.parse(host.dataset.options);
|
|
2743
|
+
Gaugeit.createGauge(host, options);
|
|
2744
|
+
});
|
|
2745
|
+
</script>
|
|
2746
|
+
@endpush
|
|
2747
|
+
```
|
|
2748
|
+
|
|
2749
|
+
The package is a browser-asset distribution. PHP does not render the SVG server-side.
|
|
2750
|
+
|
|
2751
|
+
## 14. Custom element
|
|
2752
|
+
|
|
2753
|
+
```html
|
|
2754
|
+
<!doctype html>
|
|
2755
|
+
<html lang="en">
|
|
2756
|
+
<head>
|
|
2757
|
+
<meta charset="utf-8">
|
|
2758
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2759
|
+
<title>Gaugeit.js custom element</title>
|
|
2760
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2761
|
+
</head>
|
|
2762
|
+
<body>
|
|
2763
|
+
<gauge-it
|
|
2764
|
+
id="voltage"
|
|
2765
|
+
type="classic"
|
|
2766
|
+
min="0"
|
|
2767
|
+
max="240"
|
|
2768
|
+
value="88"
|
|
2769
|
+
options='{"readout":{"title":{"visible":true,"text":"VOLTAGE"},"unit":{"visible":true,"text":"V"}},"accessibility":{"label":"Voltage"}}'
|
|
2770
|
+
></gauge-it>
|
|
2771
|
+
|
|
2772
|
+
<button id="increase" type="button">Increase</button>
|
|
2773
|
+
|
|
2774
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2775
|
+
<script>
|
|
2776
|
+
Gaugeit.defineGaugeitElement();
|
|
2777
|
+
|
|
2778
|
+
const element = document.querySelector('#voltage');
|
|
2779
|
+
|
|
2780
|
+
document.querySelector('#increase').addEventListener('click', () => {
|
|
2781
|
+
element.value = Number(element.value) + 10;
|
|
2782
|
+
});
|
|
2783
|
+
</script>
|
|
2784
|
+
</body>
|
|
2785
|
+
</html>
|
|
2786
|
+
```
|
|
2787
|
+
|
|
2788
|
+
Define the element before relying on its upgraded behavior. The `options` attribute
|
|
2789
|
+
must contain valid JSON.
|
|
2790
|
+
|
|
2791
|
+
## 15. Data-attribute auto mounting
|
|
2792
|
+
|
|
2793
|
+
```html
|
|
2794
|
+
<!doctype html>
|
|
2795
|
+
<html lang="en">
|
|
2796
|
+
<head>
|
|
2797
|
+
<meta charset="utf-8">
|
|
2798
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2799
|
+
<title>Gaugeit.js auto mounting</title>
|
|
2800
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2801
|
+
</head>
|
|
2802
|
+
<body>
|
|
2803
|
+
<div
|
|
2804
|
+
data-gaugeit
|
|
2805
|
+
data-gaugeit-type="arc"
|
|
2806
|
+
data-gaugeit-min="0"
|
|
2807
|
+
data-gaugeit-max="100"
|
|
2808
|
+
data-gaugeit-value="63"
|
|
2809
|
+
data-gaugeit-options='{"readout":{"value":{"visible":true,"suffix":"%"}},"accessibility":{"label":"Completion"}}'
|
|
2810
|
+
></div>
|
|
2811
|
+
|
|
2812
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2813
|
+
<script>
|
|
2814
|
+
const instances = Gaugeit.autoMount();
|
|
2815
|
+
|
|
2816
|
+
const first = instances[0];
|
|
2817
|
+
first?.setValue(78);
|
|
2818
|
+
</script>
|
|
2819
|
+
</body>
|
|
2820
|
+
</html>
|
|
2821
|
+
```
|
|
2822
|
+
|
|
2823
|
+
Repeated `autoMount()` calls return existing managed instances instead of duplicating
|
|
2824
|
+
them.
|
|
2825
|
+
|
|
2826
|
+
## 16. Custom gauge type from built-in layers
|
|
2827
|
+
|
|
2828
|
+
```html
|
|
2829
|
+
<!doctype html>
|
|
2830
|
+
<html lang="en">
|
|
2831
|
+
<head>
|
|
2832
|
+
<meta charset="utf-8">
|
|
2833
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2834
|
+
<title>Custom Gaugeit.js type</title>
|
|
2835
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2836
|
+
</head>
|
|
2837
|
+
<body>
|
|
2838
|
+
<div id="form-gauge"></div>
|
|
2839
|
+
|
|
2840
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2841
|
+
<script>
|
|
2842
|
+
Gaugeit.registerGaugeType('football-form', {
|
|
2843
|
+
description: 'Compact recent-form performance instrument.',
|
|
2844
|
+
|
|
2845
|
+
defaults: {
|
|
2846
|
+
min: 0,
|
|
2847
|
+
max: 100,
|
|
2848
|
+
|
|
2849
|
+
geometry: {
|
|
2850
|
+
width: 280,
|
|
2851
|
+
height: 170,
|
|
2852
|
+
centerX: 140,
|
|
2853
|
+
centerY: 148,
|
|
2854
|
+
radius: 104,
|
|
2855
|
+
startAngle: 200,
|
|
2856
|
+
endAngle: 340
|
|
2857
|
+
},
|
|
2858
|
+
|
|
2859
|
+
track: {
|
|
2860
|
+
radius: 96,
|
|
2861
|
+
width: 22
|
|
2862
|
+
},
|
|
2863
|
+
|
|
2864
|
+
scale: {
|
|
2865
|
+
radius: 96
|
|
2866
|
+
},
|
|
2867
|
+
|
|
2868
|
+
labels: {
|
|
2869
|
+
radius: 72
|
|
2870
|
+
},
|
|
2871
|
+
|
|
2872
|
+
pointer: {
|
|
2873
|
+
type: 'arrow',
|
|
2874
|
+
length: 82,
|
|
2875
|
+
width: 6,
|
|
2876
|
+
color: '#0f172a'
|
|
2877
|
+
},
|
|
2878
|
+
|
|
2879
|
+
readout: {
|
|
2880
|
+
title: {
|
|
2881
|
+
visible: true,
|
|
2882
|
+
text: 'FORM'
|
|
2883
|
+
},
|
|
2884
|
+
value: {
|
|
2885
|
+
visible: true,
|
|
2886
|
+
suffix: '%'
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
},
|
|
2890
|
+
|
|
2891
|
+
layers: [
|
|
2892
|
+
Gaugeit.layers.face,
|
|
2893
|
+
Gaugeit.layers.zones,
|
|
2894
|
+
Gaugeit.layers.ticks,
|
|
2895
|
+
Gaugeit.layers.labels,
|
|
2896
|
+
Gaugeit.layers.light,
|
|
2897
|
+
Gaugeit.layers.readout,
|
|
2898
|
+
Gaugeit.layers.pointer
|
|
2899
|
+
]
|
|
2900
|
+
});
|
|
2901
|
+
|
|
2902
|
+
Gaugeit.createGauge('#form-gauge', {
|
|
2903
|
+
type: 'football-form',
|
|
2904
|
+
value: 74,
|
|
2905
|
+
zones: [
|
|
2906
|
+
{ min: 0, max: 40, color: '#ef4444' },
|
|
2907
|
+
{ min: 40, max: 70, color: '#facc15' },
|
|
2908
|
+
{ min: 70, max: 100, color: '#22c55e' }
|
|
2909
|
+
],
|
|
2910
|
+
accessibility: {
|
|
2911
|
+
label: 'Recent form'
|
|
2912
|
+
}
|
|
2913
|
+
});
|
|
2914
|
+
</script>
|
|
2915
|
+
</body>
|
|
2916
|
+
</html>
|
|
2917
|
+
```
|
|
2918
|
+
|
|
2919
|
+
The pointer is placed after the readout in the layer array, so it paints above text.
|
|
2920
|
+
|
|
2921
|
+
## 17. Custom dynamic layer
|
|
2922
|
+
|
|
2923
|
+
```js
|
|
2924
|
+
const targetMarkerLayer = {
|
|
2925
|
+
id: 'target-marker',
|
|
2926
|
+
|
|
2927
|
+
render({ options, renderer }) {
|
|
2928
|
+
const group = renderer.group('target-marker', {
|
|
2929
|
+
'aria-hidden': 'true'
|
|
2930
|
+
});
|
|
2931
|
+
|
|
2932
|
+
const marker = document.createElementNS(
|
|
2933
|
+
'http://www.w3.org/2000/svg',
|
|
2934
|
+
'circle'
|
|
2935
|
+
);
|
|
2936
|
+
|
|
2937
|
+
marker.setAttribute('r', String(options.targetMarker.radius));
|
|
2938
|
+
marker.setAttribute('fill', options.targetMarker.color);
|
|
2939
|
+
group.append(marker);
|
|
2940
|
+
|
|
2941
|
+
return {
|
|
2942
|
+
update(value) {
|
|
2943
|
+
const point = Gaugeit.geometry.polarPoint(
|
|
2944
|
+
options.geometry.centerX,
|
|
2945
|
+
options.geometry.centerY,
|
|
2946
|
+
options.targetMarker.distance,
|
|
2947
|
+
Gaugeit.geometry.valueToAngle(value, options)
|
|
2948
|
+
);
|
|
2949
|
+
|
|
2950
|
+
marker.setAttribute('cx', String(point.x));
|
|
2951
|
+
marker.setAttribute('cy', String(point.y));
|
|
2952
|
+
}
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
};
|
|
2956
|
+
|
|
2957
|
+
Gaugeit.registerGaugeType('target-marker-gauge', {
|
|
2958
|
+
defaults: {
|
|
2959
|
+
targetMarker: {
|
|
2960
|
+
radius: 4,
|
|
2961
|
+
distance: 102,
|
|
2962
|
+
color: '#2563eb'
|
|
2963
|
+
}
|
|
2964
|
+
},
|
|
2965
|
+
|
|
2966
|
+
layers: [
|
|
2967
|
+
Gaugeit.layers.face,
|
|
2968
|
+
Gaugeit.layers.zones,
|
|
2969
|
+
Gaugeit.layers.ticks,
|
|
2970
|
+
Gaugeit.layers.labels,
|
|
2971
|
+
targetMarkerLayer,
|
|
2972
|
+
Gaugeit.layers.readout,
|
|
2973
|
+
Gaugeit.layers.pointer
|
|
2974
|
+
]
|
|
2975
|
+
});
|
|
2976
|
+
```
|
|
2977
|
+
|
|
2978
|
+
Use controller `value` for stable animated display semantics. Use
|
|
2979
|
+
`state.pointerValue` only if the custom artwork should include jitter.
|
|
2980
|
+
|
|
2981
|
+
## 18. Destroy and recreate safely
|
|
2982
|
+
|
|
2983
|
+
```html
|
|
2984
|
+
<!doctype html>
|
|
2985
|
+
<html lang="en">
|
|
2986
|
+
<head>
|
|
2987
|
+
<meta charset="utf-8">
|
|
2988
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2989
|
+
<title>Gaugeit.js lifecycle</title>
|
|
2990
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
2991
|
+
</head>
|
|
2992
|
+
<body>
|
|
2993
|
+
<div id="gauge"></div>
|
|
2994
|
+
<button id="recreate" type="button">Recreate</button>
|
|
2995
|
+
<button id="destroy" type="button">Destroy</button>
|
|
2996
|
+
|
|
2997
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
2998
|
+
<script>
|
|
2999
|
+
const host = document.querySelector('#gauge');
|
|
3000
|
+
let gauge = create();
|
|
3001
|
+
|
|
3002
|
+
function create() {
|
|
3003
|
+
return Gaugeit.createGauge(host, {
|
|
3004
|
+
type: 'arc',
|
|
3005
|
+
min: 0,
|
|
3006
|
+
max: 100,
|
|
3007
|
+
value: 50,
|
|
3008
|
+
accessibility: {
|
|
3009
|
+
label: 'Lifecycle demonstration'
|
|
3010
|
+
}
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
document.querySelector('#recreate').addEventListener('click', () => {
|
|
3015
|
+
gauge.destroy();
|
|
3016
|
+
gauge = create();
|
|
3017
|
+
});
|
|
3018
|
+
|
|
3019
|
+
document.querySelector('#destroy').addEventListener('click', () => {
|
|
3020
|
+
gauge.destroy();
|
|
3021
|
+
});
|
|
3022
|
+
</script>
|
|
3023
|
+
</body>
|
|
3024
|
+
</html>
|
|
3025
|
+
```
|
|
3026
|
+
|
|
3027
|
+
`createGauge()` is managed and already destroys an existing managed gauge on the same
|
|
3028
|
+
host. Explicit destruction is shown because it makes ownership clear and is required
|
|
3029
|
+
when the application is removing the component permanently.
|
|
3030
|
+
|
|
3031
|
+
## 19. Listen for value and animation completion
|
|
3032
|
+
|
|
3033
|
+
```js
|
|
3034
|
+
const host = document.querySelector('#gauge');
|
|
3035
|
+
const gauge = Gaugeit.createGauge(host, {
|
|
3036
|
+
type: 'arc',
|
|
3037
|
+
value: 10
|
|
3038
|
+
});
|
|
3039
|
+
|
|
3040
|
+
host.addEventListener('gaugeit:change', (event) => {
|
|
3041
|
+
console.log('Requested value:', event.detail.value);
|
|
3042
|
+
});
|
|
3043
|
+
|
|
3044
|
+
host.addEventListener('gaugeit:displaychange', (event) => {
|
|
3045
|
+
console.log('Displayed value:', event.detail.displayedValue);
|
|
3046
|
+
});
|
|
3047
|
+
|
|
3048
|
+
host.addEventListener('gaugeit:settled', (event) => {
|
|
3049
|
+
console.log('Animation settled:', event.detail.displayedValue);
|
|
3050
|
+
});
|
|
3051
|
+
|
|
3052
|
+
gauge.setValue(90);
|
|
3053
|
+
```
|
|
3054
|
+
|
|
3055
|
+
Use host-scoped namespaced events. Do not install an unscoped global callback API.
|
|
3056
|
+
|
|
3057
|
+
## 20. Switch complete configurations
|
|
3058
|
+
|
|
3059
|
+
Use `replaceOptions()` when switching instrument modes:
|
|
3060
|
+
|
|
3061
|
+
```js
|
|
3062
|
+
const gauge = Gaugeit.createGauge('#instrument', {
|
|
3063
|
+
type: 'arc',
|
|
3064
|
+
min: 0,
|
|
3065
|
+
max: 100,
|
|
3066
|
+
value: 40
|
|
3067
|
+
});
|
|
3068
|
+
|
|
3069
|
+
function showTemperature(value) {
|
|
3070
|
+
gauge.replaceOptions({
|
|
3071
|
+
type: 'classic',
|
|
3072
|
+
min: -20,
|
|
3073
|
+
max: 120,
|
|
3074
|
+
value,
|
|
3075
|
+
readout: {
|
|
3076
|
+
title: {
|
|
3077
|
+
visible: true,
|
|
3078
|
+
text: 'TEMPERATURE'
|
|
3079
|
+
},
|
|
3080
|
+
value: {
|
|
3081
|
+
visible: true
|
|
3082
|
+
},
|
|
3083
|
+
unit: {
|
|
3084
|
+
visible: true,
|
|
3085
|
+
text: '°C'
|
|
3086
|
+
}
|
|
3087
|
+
},
|
|
3088
|
+
accessibility: {
|
|
3089
|
+
label: 'Temperature'
|
|
3090
|
+
}
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
function showBalance(value) {
|
|
3095
|
+
gauge.replaceOptions({
|
|
3096
|
+
type: 'center-zero',
|
|
3097
|
+
min: -100,
|
|
3098
|
+
max: 100,
|
|
3099
|
+
value,
|
|
3100
|
+
readout: {
|
|
3101
|
+
title: {
|
|
3102
|
+
visible: true,
|
|
3103
|
+
text: 'BALANCE'
|
|
3104
|
+
},
|
|
3105
|
+
value: {
|
|
3106
|
+
visible: true
|
|
3107
|
+
}
|
|
3108
|
+
},
|
|
3109
|
+
accessibility: {
|
|
3110
|
+
label: 'Balance'
|
|
3111
|
+
}
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
```
|
|
3115
|
+
|
|
3116
|
+
`updateOptions()` would retain earlier overrides that are omitted from the new mode.
|
|
3117
|
+
`replaceOptions()` resets omitted keys to the new type defaults.
|
|
3118
|
+
|
|
3119
|
+
## Recipe verification checklist
|
|
3120
|
+
|
|
3121
|
+
Before returning generated integration code, verify:
|
|
3122
|
+
|
|
3123
|
+
- JavaScript distribution matches the environment.
|
|
3124
|
+
- CSS is loaded unless standalone is used.
|
|
3125
|
+
- The global is `Gaugeit`.
|
|
3126
|
+
- Package import paths use lowercase `gaugeit.js`.
|
|
3127
|
+
- The React component is `Gaugeit`.
|
|
3128
|
+
- The target exists before creation.
|
|
3129
|
+
- The selected built-in type name is exact.
|
|
3130
|
+
- Center-zero range contains its zero reference.
|
|
3131
|
+
- Readout child visibility is explicit.
|
|
3132
|
+
- Manual lights use `trigger.mode: 'manual'`.
|
|
3133
|
+
- Blink/pulse precedence is intentional.
|
|
3134
|
+
- Application timers and listeners have cleanup.
|
|
3135
|
+
- Component unmount destroys an owned instance.
|
|
3136
|
+
- CDN URLs pin `0.1.1`.
|
|
3137
|
+
- Accessibility label and value text are meaningful.
|
|
3138
|
+
|
|
3139
|
+
---
|
|
3140
|
+
|
|
3141
|
+
# Part IV — Contributor architecture and workflow
|
|
3142
|
+
|
|
3143
|
+
This guide explains how to modify and extend Gaugeit.js 0.1.1 without breaking its
|
|
3144
|
+
public API, distribution model, responsive layout, or cross-platform reproducibility.
|
|
3145
|
+
|
|
3146
|
+
Read root `AGENTS.md` first. Existing human-oriented references remain important:
|
|
3147
|
+
|
|
3148
|
+
- `docs/architecture.md`
|
|
3149
|
+
- `docs/creating-gauge-types.md`
|
|
3150
|
+
- `docs/development.md`
|
|
3151
|
+
- `docs/api.md`
|
|
3152
|
+
- `CONTRIBUTING.md`
|
|
3153
|
+
|
|
3154
|
+
## Architecture summary
|
|
3155
|
+
|
|
3156
|
+
Gaugeit.js separates application state from visual composition:
|
|
3157
|
+
|
|
3158
|
+
```text
|
|
3159
|
+
public API
|
|
3160
|
+
-> managed instance creation
|
|
3161
|
+
-> Gauge lifecycle and animation core
|
|
3162
|
+
-> option resolution and selected type
|
|
3163
|
+
-> ordered rendering layers
|
|
3164
|
+
-> SVG renderer and dynamic controllers
|
|
3165
|
+
-> generated responsive instrument
|
|
3166
|
+
```
|
|
3167
|
+
|
|
3168
|
+
The core owns values, animation, jitter, lifecycle, events, accessibility, and cleanup.
|
|
3169
|
+
Types own defaults and ordered layer lists. Layers own SVG. Geometry helpers own
|
|
3170
|
+
deterministic math. This separation is a project invariant.
|
|
3171
|
+
|
|
3172
|
+
## Canonical source and generated output
|
|
3173
|
+
|
|
3174
|
+
Canonical files:
|
|
3175
|
+
|
|
3176
|
+
```text
|
|
3177
|
+
src/index.js
|
|
3178
|
+
src/core/
|
|
3179
|
+
src/geometry/
|
|
3180
|
+
src/layers/
|
|
3181
|
+
src/renderer/
|
|
3182
|
+
src/types/
|
|
3183
|
+
src/gaugeit.css
|
|
3184
|
+
src/index.d.ts
|
|
3185
|
+
adapters/
|
|
3186
|
+
bin/
|
|
3187
|
+
scripts/
|
|
3188
|
+
tests/
|
|
3189
|
+
README.md
|
|
3190
|
+
docs/
|
|
3191
|
+
```
|
|
3192
|
+
|
|
3193
|
+
Generated files:
|
|
3194
|
+
|
|
3195
|
+
```text
|
|
3196
|
+
dist/gaugeit.cjs
|
|
3197
|
+
dist/gaugeit.cjs.map
|
|
3198
|
+
dist/gaugeit.min.cjs
|
|
3199
|
+
dist/gaugeit.min.cjs.map
|
|
3200
|
+
dist/gaugeit.esm.js
|
|
3201
|
+
dist/gaugeit.esm.js.map
|
|
3202
|
+
dist/gaugeit.esm.min.js
|
|
3203
|
+
dist/gaugeit.esm.min.js.map
|
|
3204
|
+
dist/gaugeit.umd.js
|
|
3205
|
+
dist/gaugeit.umd.js.map
|
|
3206
|
+
dist/gaugeit.umd.min.js
|
|
3207
|
+
dist/gaugeit.umd.min.js.map
|
|
3208
|
+
dist/gaugeit.standalone.js
|
|
3209
|
+
dist/gaugeit.standalone.js.map
|
|
3210
|
+
dist/gaugeit.standalone.min.js
|
|
3211
|
+
dist/gaugeit.standalone.min.js.map
|
|
3212
|
+
dist/gaugeit.css
|
|
3213
|
+
dist/gaugeit.min.css
|
|
3214
|
+
dist/gaugeit.d.ts
|
|
3215
|
+
dist/manifest.json
|
|
3216
|
+
```
|
|
3217
|
+
|
|
3218
|
+
Never hand-edit generated files. A source change is incomplete until the generated
|
|
3219
|
+
distribution is rebuilt and committed.
|
|
3220
|
+
|
|
3221
|
+
## Option resolution pipeline
|
|
3222
|
+
|
|
3223
|
+
The runtime resolves options in `src/core/options.js`.
|
|
3224
|
+
|
|
3225
|
+
### Stage 1: determine type
|
|
3226
|
+
|
|
3227
|
+
The requested `type` is converted to a string and looked up in the registry. Unknown
|
|
3228
|
+
types do not receive a silent fallback because a typo would otherwise hide an
|
|
3229
|
+
integration error.
|
|
3230
|
+
|
|
3231
|
+
### Stage 2: merge defaults
|
|
3232
|
+
|
|
3233
|
+
Resolution order:
|
|
3234
|
+
|
|
3235
|
+
```text
|
|
3236
|
+
BASE_DEFAULTS
|
|
3237
|
+
+ selected type defaults
|
|
3238
|
+
+ raw instance options
|
|
3239
|
+
```
|
|
3240
|
+
|
|
3241
|
+
The merge rules in `src/core/utils.js` are:
|
|
3242
|
+
|
|
3243
|
+
- plain objects merge recursively;
|
|
3244
|
+
- arrays are replaced;
|
|
3245
|
+
- callback functions are preserved;
|
|
3246
|
+
- unsafe keys `__proto__`, `prototype`, and `constructor` are ignored;
|
|
3247
|
+
- cross-realm plain objects remain accepted.
|
|
3248
|
+
|
|
3249
|
+
### Stage 3: restore nested shapes
|
|
3250
|
+
|
|
3251
|
+
Malformed nested values are restored from fallback objects. This prevents common
|
|
3252
|
+
configuration mistakes such as:
|
|
3253
|
+
|
|
3254
|
+
```js
|
|
3255
|
+
{ pointer: null }
|
|
3256
|
+
```
|
|
3257
|
+
|
|
3258
|
+
from crashing later layer code.
|
|
3259
|
+
|
|
3260
|
+
### Stage 4: optional type configuration
|
|
3261
|
+
|
|
3262
|
+
A type may expose:
|
|
3263
|
+
|
|
3264
|
+
```js
|
|
3265
|
+
configure(options, {
|
|
3266
|
+
requested,
|
|
3267
|
+
typeDefaults
|
|
3268
|
+
})
|
|
3269
|
+
```
|
|
3270
|
+
|
|
3271
|
+
This runs after merge and before final validation.
|
|
3272
|
+
|
|
3273
|
+
Use the hook only to derive unspecified fallback values. The resizable built-in linear
|
|
3274
|
+
and tape types use it to scale preset geometry from requested dimensions. Do not
|
|
3275
|
+
overwrite coordinates explicitly provided by the instance.
|
|
3276
|
+
|
|
3277
|
+
### Stage 5: validate and normalize
|
|
3278
|
+
|
|
3279
|
+
Validation:
|
|
3280
|
+
|
|
3281
|
+
- repairs invalid numeric ranges;
|
|
3282
|
+
- clamps sizes and opacity;
|
|
3283
|
+
- checks enum values;
|
|
3284
|
+
- normalizes zones;
|
|
3285
|
+
- swaps reversed trigger bounds;
|
|
3286
|
+
- derives light glow radius when null;
|
|
3287
|
+
- enforces minimum intervals;
|
|
3288
|
+
- preserves compatibility aliases.
|
|
3289
|
+
|
|
3290
|
+
Do not move ordinary defensive normalization into rendering layers. Layers should
|
|
3291
|
+
receive resolved runtime options.
|
|
3292
|
+
|
|
3293
|
+
## User option ownership
|
|
3294
|
+
|
|
3295
|
+
`Gauge` stores two related configurations:
|
|
3296
|
+
|
|
3297
|
+
- `userOptions`: original declarative overrides;
|
|
3298
|
+
- `options`: fully resolved runtime configuration.
|
|
3299
|
+
|
|
3300
|
+
`updateOptions(patch)` deep-merges into `userOptions`.
|
|
3301
|
+
|
|
3302
|
+
`replaceOptions(options)` replaces `userOptions` with a fresh object.
|
|
3303
|
+
|
|
3304
|
+
Both re-resolve the selected type, reconfigure lifecycle observers, render static
|
|
3305
|
+
content again, and preserve the current target value unless an explicit new value is
|
|
3306
|
+
provided.
|
|
3307
|
+
|
|
3308
|
+
When adding a public option, consider both patch and replacement semantics.
|
|
3309
|
+
|
|
3310
|
+
## Type registry
|
|
3311
|
+
|
|
3312
|
+
`GaugeTypeRegistry` stores:
|
|
3313
|
+
|
|
3314
|
+
```js
|
|
3315
|
+
{
|
|
3316
|
+
name,
|
|
3317
|
+
defaults,
|
|
3318
|
+
layers,
|
|
3319
|
+
description,
|
|
3320
|
+
configure
|
|
3321
|
+
}
|
|
3322
|
+
```
|
|
3323
|
+
|
|
3324
|
+
Registration validates:
|
|
3325
|
+
|
|
3326
|
+
- non-empty type name;
|
|
3327
|
+
- plain-object definition;
|
|
3328
|
+
- non-empty layer array;
|
|
3329
|
+
- every layer exposes `render(context)`.
|
|
3330
|
+
|
|
3331
|
+
Public registry operations:
|
|
3332
|
+
|
|
3333
|
+
```js
|
|
3334
|
+
registry.register(name, definition)
|
|
3335
|
+
registry.get(name)
|
|
3336
|
+
registry.has(name)
|
|
3337
|
+
registry.names()
|
|
3338
|
+
registry.describe()
|
|
3339
|
+
```
|
|
3340
|
+
|
|
3341
|
+
Built-ins are registered in `src/types/register-builtins.js` during module
|
|
3342
|
+
initialization.
|
|
3343
|
+
|
|
3344
|
+
When adding a built-in type:
|
|
3345
|
+
|
|
3346
|
+
1. create `src/types/<name>.js`;
|
|
3347
|
+
2. provide description, defaults, layers, and optional configure hook;
|
|
3348
|
+
3. register it in `register-builtins.js`;
|
|
3349
|
+
4. expose it through `builtInTypes`;
|
|
3350
|
+
5. ensure all public colors and dimensions remain overridable;
|
|
3351
|
+
6. add type-specific tests;
|
|
3352
|
+
7. update README, API and AI docs;
|
|
3353
|
+
8. rebuild `dist/`.
|
|
3354
|
+
|
|
3355
|
+
## Type defaults
|
|
3356
|
+
|
|
3357
|
+
A type should express its visual identity through defaults, not custom branches in the
|
|
3358
|
+
core.
|
|
3359
|
+
|
|
3360
|
+
Prefer:
|
|
3361
|
+
|
|
3362
|
+
```js
|
|
3363
|
+
export const exampleGaugeType = {
|
|
3364
|
+
description: '...',
|
|
3365
|
+
defaults: {
|
|
3366
|
+
geometry: { ... },
|
|
3367
|
+
face: { ... },
|
|
3368
|
+
pointer: { ... }
|
|
3369
|
+
},
|
|
3370
|
+
layers: [ ... ]
|
|
3371
|
+
};
|
|
3372
|
+
```
|
|
3373
|
+
|
|
3374
|
+
Avoid:
|
|
3375
|
+
|
|
3376
|
+
```js
|
|
3377
|
+
if (options.type === 'example') {
|
|
3378
|
+
// special behavior in the generic core
|
|
3379
|
+
}
|
|
3380
|
+
```
|
|
3381
|
+
|
|
3382
|
+
A new geometry family may require new pure helpers and layers, but the lifecycle core
|
|
3383
|
+
should remain type-agnostic.
|
|
3384
|
+
|
|
3385
|
+
## Layer execution and paint order
|
|
3386
|
+
|
|
3387
|
+
A layer has the public shape:
|
|
3388
|
+
|
|
3389
|
+
```js
|
|
3390
|
+
{
|
|
3391
|
+
id: 'layer-name',
|
|
3392
|
+
|
|
3393
|
+
render(context) {
|
|
3394
|
+
// append static SVG
|
|
3395
|
+
|
|
3396
|
+
return {
|
|
3397
|
+
update(value, state) {
|
|
3398
|
+
// optional dynamic work
|
|
3399
|
+
},
|
|
3400
|
+
|
|
3401
|
+
destroy() {
|
|
3402
|
+
// optional cleanup
|
|
3403
|
+
}
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
```
|
|
3408
|
+
|
|
3409
|
+
Render context contains:
|
|
3410
|
+
|
|
3411
|
+
```js
|
|
3412
|
+
{
|
|
3413
|
+
gauge,
|
|
3414
|
+
options,
|
|
3415
|
+
renderer,
|
|
3416
|
+
registry
|
|
3417
|
+
}
|
|
3418
|
+
```
|
|
3419
|
+
|
|
3420
|
+
Layer-array order is SVG paint order. Later groups appear above earlier groups.
|
|
3421
|
+
|
|
3422
|
+
Built-in radial types intentionally order:
|
|
3423
|
+
|
|
3424
|
+
```text
|
|
3425
|
+
face
|
|
3426
|
+
zones
|
|
3427
|
+
ticks
|
|
3428
|
+
labels
|
|
3429
|
+
light
|
|
3430
|
+
readout
|
|
3431
|
+
pointer
|
|
3432
|
+
```
|
|
3433
|
+
|
|
3434
|
+
so the pointer paints above the readout.
|
|
3435
|
+
|
|
3436
|
+
Built-in linear types place the indicator after readout for the same reason.
|
|
3437
|
+
|
|
3438
|
+
Do not attempt to solve SVG stacking with a numeric CSS `z-index`. Use layer order and
|
|
3439
|
+
the existing pointer/indicator cap `stackOrder`.
|
|
3440
|
+
|
|
3441
|
+
## Static versus dynamic work
|
|
3442
|
+
|
|
3443
|
+
Static layer examples:
|
|
3444
|
+
|
|
3445
|
+
- face;
|
|
3446
|
+
- zones;
|
|
3447
|
+
- ticks;
|
|
3448
|
+
- labels;
|
|
3449
|
+
- fixed zero marker.
|
|
3450
|
+
|
|
3451
|
+
Dynamic layer examples:
|
|
3452
|
+
|
|
3453
|
+
- radial pointer;
|
|
3454
|
+
- linear indicator;
|
|
3455
|
+
- rolling tape slots;
|
|
3456
|
+
- readout value;
|
|
3457
|
+
- light state.
|
|
3458
|
+
|
|
3459
|
+
Static geometry should be computed once during `render()`. `update()` should mutate
|
|
3460
|
+
only the smallest owned set of nodes.
|
|
3461
|
+
|
|
3462
|
+
Avoid rebuilding whole SVG groups in every animation frame.
|
|
3463
|
+
|
|
3464
|
+
## Renderer ownership
|
|
3465
|
+
|
|
3466
|
+
`SvgRenderer`:
|
|
3467
|
+
|
|
3468
|
+
- creates the generated root and SVG;
|
|
3469
|
+
- creates unique IDs for definitions;
|
|
3470
|
+
- owns layer groups;
|
|
3471
|
+
- owns controller registration;
|
|
3472
|
+
- owns a small reference map;
|
|
3473
|
+
- applies content clipping;
|
|
3474
|
+
- calculates and applies final viewBox;
|
|
3475
|
+
- removes generated content on destruction.
|
|
3476
|
+
|
|
3477
|
+
Use:
|
|
3478
|
+
|
|
3479
|
+
```js
|
|
3480
|
+
const group = renderer.group('my-layer', {
|
|
3481
|
+
'aria-hidden': 'true'
|
|
3482
|
+
});
|
|
3483
|
+
```
|
|
3484
|
+
|
|
3485
|
+
instead of appending directly to the host or another application-owned node.
|
|
3486
|
+
|
|
3487
|
+
The renderer may apply a face clip path to groups created after the face layer. Bypass
|
|
3488
|
+
of `renderer.group()` can break clipping and ownership.
|
|
3489
|
+
|
|
3490
|
+
## Renderer references
|
|
3491
|
+
|
|
3492
|
+
A layer may store generated nodes or values:
|
|
3493
|
+
|
|
3494
|
+
```js
|
|
3495
|
+
renderer.setReference('name', value);
|
|
3496
|
+
const value = renderer.getReference('name');
|
|
3497
|
+
```
|
|
3498
|
+
|
|
3499
|
+
Use references sparingly for coordination within one generated instrument. Do not use
|
|
3500
|
+
global registries for per-instance SVG nodes.
|
|
3501
|
+
|
|
3502
|
+
## Dynamic controller state
|
|
3503
|
+
|
|
3504
|
+
Controller signature:
|
|
3505
|
+
|
|
3506
|
+
```js
|
|
3507
|
+
update(value, state)
|
|
3508
|
+
```
|
|
3509
|
+
|
|
3510
|
+
`value` is the rendered base value after animation and precision handling.
|
|
3511
|
+
|
|
3512
|
+
State fields:
|
|
3513
|
+
|
|
3514
|
+
```js
|
|
3515
|
+
{
|
|
3516
|
+
gauge,
|
|
3517
|
+
targetValue,
|
|
3518
|
+
baseValue,
|
|
3519
|
+
pointerValue,
|
|
3520
|
+
animated,
|
|
3521
|
+
jitterOffset
|
|
3522
|
+
}
|
|
3523
|
+
```
|
|
3524
|
+
|
|
3525
|
+
Choose intentionally:
|
|
3526
|
+
|
|
3527
|
+
- `targetValue`: stable application intent;
|
|
3528
|
+
- `value` or `baseValue`: animated displayed semantics;
|
|
3529
|
+
- `pointerValue`: animated value plus visual jitter.
|
|
3530
|
+
|
|
3531
|
+
Readouts and accessibility should not use `pointerValue`. A realistic moving needle or
|
|
3532
|
+
tape should.
|
|
3533
|
+
|
|
3534
|
+
The shared light exposes this decision as `light.trigger.source`.
|
|
3535
|
+
|
|
3536
|
+
## Gauge lifecycle
|
|
3537
|
+
|
|
3538
|
+
Construction:
|
|
3539
|
+
|
|
3540
|
+
1. resolve target;
|
|
3541
|
+
2. resolve user options;
|
|
3542
|
+
3. create renderer;
|
|
3543
|
+
4. establish initial target/base/display/pointer state;
|
|
3544
|
+
5. render static layers;
|
|
3545
|
+
6. install lifecycle observers;
|
|
3546
|
+
7. perform startup animation if enabled.
|
|
3547
|
+
|
|
3548
|
+
### `setValue()`
|
|
3549
|
+
|
|
3550
|
+
- normalizes according to overflow;
|
|
3551
|
+
- updates target and resolved value;
|
|
3552
|
+
- updates immediately or creates one animation;
|
|
3553
|
+
- emits `gaugeit:change`;
|
|
3554
|
+
- schedules the frame loop only when needed.
|
|
3555
|
+
|
|
3556
|
+
### Frame loop
|
|
3557
|
+
|
|
3558
|
+
One `requestAnimationFrame` loop per active gauge:
|
|
3559
|
+
|
|
3560
|
+
1. advance base animation;
|
|
3561
|
+
2. choose a new jitter target at configured frequency;
|
|
3562
|
+
3. smooth jitter offset;
|
|
3563
|
+
4. calculate displayed and pointer-only values;
|
|
3564
|
+
5. update dynamic controllers;
|
|
3565
|
+
6. update accessibility and display events;
|
|
3566
|
+
7. stop when no animation or jitter remains.
|
|
3567
|
+
|
|
3568
|
+
### Pause and lifecycle suspension
|
|
3569
|
+
|
|
3570
|
+
Manual `pause()` and `resume()` preserve elapsed animation time.
|
|
3571
|
+
|
|
3572
|
+
Page visibility and intersection observers suspend work when configured. Option
|
|
3573
|
+
replacement reconfigures observers.
|
|
3574
|
+
|
|
3575
|
+
### Destruction
|
|
3576
|
+
|
|
3577
|
+
`destroy()` must:
|
|
3578
|
+
|
|
3579
|
+
- cancel animation frame;
|
|
3580
|
+
- clear animation;
|
|
3581
|
+
- disconnect observers;
|
|
3582
|
+
- remove listeners;
|
|
3583
|
+
- call controller cleanup;
|
|
3584
|
+
- remove generated DOM;
|
|
3585
|
+
- remove managed-instance mapping;
|
|
3586
|
+
- emit destruction event.
|
|
3587
|
+
|
|
3588
|
+
Any new layer timer, observer, or listener must be released by its controller
|
|
3589
|
+
`destroy()`.
|
|
3590
|
+
|
|
3591
|
+
## Animation model
|
|
3592
|
+
|
|
3593
|
+
Application value and visual realism are separate:
|
|
3594
|
+
|
|
3595
|
+
```text
|
|
3596
|
+
target value
|
|
3597
|
+
-> base animation
|
|
3598
|
+
-> displayed value
|
|
3599
|
+
-> optional jitter offset
|
|
3600
|
+
-> pointer value
|
|
3601
|
+
```
|
|
3602
|
+
|
|
3603
|
+
This ensures:
|
|
3604
|
+
|
|
3605
|
+
- `getValue()` is stable;
|
|
3606
|
+
- `getDisplayedValue()` may animate but never jitters;
|
|
3607
|
+
- readout and ARIA values never jitter;
|
|
3608
|
+
- meaningful events never jitter;
|
|
3609
|
+
- moving artwork may jitter.
|
|
3610
|
+
|
|
3611
|
+
Do not merge jitter into the application value.
|
|
3612
|
+
|
|
3613
|
+
## Easing
|
|
3614
|
+
|
|
3615
|
+
Named easing resolution belongs in `src/core/easing.js`. Public `easingNames` expose
|
|
3616
|
+
supported names. A caller may also provide a callback.
|
|
3617
|
+
|
|
3618
|
+
When adding an easing:
|
|
3619
|
+
|
|
3620
|
+
- keep input/output semantics compatible with progress `0 ... 1`;
|
|
3621
|
+
- update tests and documentation;
|
|
3622
|
+
- do not add an animation dependency.
|
|
3623
|
+
|
|
3624
|
+
## Radial geometry
|
|
3625
|
+
|
|
3626
|
+
Primary helpers in `src/geometry/scale.js` include:
|
|
3627
|
+
|
|
3628
|
+
```text
|
|
3629
|
+
degreesToRadians
|
|
3630
|
+
polarPoint
|
|
3631
|
+
gaugeSweep
|
|
3632
|
+
centeredArcAngles
|
|
3633
|
+
valueToAngle
|
|
3634
|
+
angleToValue
|
|
3635
|
+
arcPath
|
|
3636
|
+
taperedArcPath
|
|
3637
|
+
niceInterval
|
|
3638
|
+
buildScaleValues
|
|
3639
|
+
buildMinorValues
|
|
3640
|
+
resolveZoneSegments
|
|
3641
|
+
findZoneForValue
|
|
3642
|
+
```
|
|
3643
|
+
|
|
3644
|
+
Important invariants:
|
|
3645
|
+
|
|
3646
|
+
- clockwise and counter-clockwise sweeps work;
|
|
3647
|
+
- sweep is limited to one full revolution;
|
|
3648
|
+
- complete circles use two SVG arc commands;
|
|
3649
|
+
- closed scales may deduplicate one endpoint;
|
|
3650
|
+
- inversion changes mapping, not numeric zone definitions;
|
|
3651
|
+
- later overlapping zones win;
|
|
3652
|
+
- transparent intervals remain true gaps;
|
|
3653
|
+
- tapered zones preserve width progression across split intervals.
|
|
3654
|
+
|
|
3655
|
+
Keep pure helpers DOM-independent.
|
|
3656
|
+
|
|
3657
|
+
## Linear geometry
|
|
3658
|
+
|
|
3659
|
+
Primary helpers in `src/geometry/linear.js` include:
|
|
3660
|
+
|
|
3661
|
+
```text
|
|
3662
|
+
linearYToSvg
|
|
3663
|
+
linearAxis
|
|
3664
|
+
valueToLinearRatio
|
|
3665
|
+
valueToLinearPoint
|
|
3666
|
+
linearPointToValue
|
|
3667
|
+
offsetLinearPoint
|
|
3668
|
+
linearNormalOffset
|
|
3669
|
+
linearIndicatorNormalOffset
|
|
3670
|
+
linearNormalSpan
|
|
3671
|
+
```
|
|
3672
|
+
|
|
3673
|
+
Public Y semantics are positive-up around `linear.originY`.
|
|
3674
|
+
|
|
3675
|
+
The two-point axis supports horizontal, vertical, and diagonal instruments. Do not
|
|
3676
|
+
assume a horizontal axis in new layers.
|
|
3677
|
+
|
|
3678
|
+
Use tangent and normal vectors from `linearAxis()` instead of special-casing
|
|
3679
|
+
orientation.
|
|
3680
|
+
|
|
3681
|
+
## Rolling-tape geometry
|
|
3682
|
+
|
|
3683
|
+
Primary helpers in `src/geometry/tape.js` include:
|
|
3684
|
+
|
|
3685
|
+
```text
|
|
3686
|
+
tapeWindowBox
|
|
3687
|
+
resolveTapeInterval
|
|
3688
|
+
tapeMinorStep
|
|
3689
|
+
tapeMinorSpacing
|
|
3690
|
+
tapeValueOffset
|
|
3691
|
+
```
|
|
3692
|
+
|
|
3693
|
+
The rolling-tape layer uses a bounded recyclable slot set around the current value.
|
|
3694
|
+
Never render one node per possible numeric value.
|
|
3695
|
+
|
|
3696
|
+
The fixed indicator remains stationary. The strip and labels move.
|
|
3697
|
+
|
|
3698
|
+
## Light geometry and timing
|
|
3699
|
+
|
|
3700
|
+
Pure helpers in `src/geometry/light.js` resolve:
|
|
3701
|
+
|
|
3702
|
+
- logical position;
|
|
3703
|
+
- manual, below, above, and between trigger state.
|
|
3704
|
+
|
|
3705
|
+
The layer owns:
|
|
3706
|
+
|
|
3707
|
+
- palettes;
|
|
3708
|
+
- gradients and blur;
|
|
3709
|
+
- physical unlit lens;
|
|
3710
|
+
- active lens;
|
|
3711
|
+
- halo;
|
|
3712
|
+
- blink/pulse CSS state;
|
|
3713
|
+
- randomized flicker timer;
|
|
3714
|
+
- controller cleanup.
|
|
3715
|
+
|
|
3716
|
+
Version 0.1.1 separates:
|
|
3717
|
+
|
|
3718
|
+
- physical `radius`;
|
|
3719
|
+
- halo `glowRadius`;
|
|
3720
|
+
- lens `opacity`;
|
|
3721
|
+
- halo `glowOpacity`;
|
|
3722
|
+
- halo `glowSharpness`.
|
|
3723
|
+
|
|
3724
|
+
Bounds must reserve the rendered halo plus blur. If rendering math changes, update
|
|
3725
|
+
`includeLight()` in `src/geometry/bounds.js` and tests.
|
|
3726
|
+
|
|
3727
|
+
Blink wins over pulse. Flicker may overlay either. All moving effects respect reduced
|
|
3728
|
+
motion.
|
|
3729
|
+
|
|
3730
|
+
## Intrinsic bounds
|
|
3731
|
+
|
|
3732
|
+
Bounds are a correctness feature, not cosmetic metadata.
|
|
3733
|
+
|
|
3734
|
+
`calculateGaugeBounds()` conservatively includes:
|
|
3735
|
+
|
|
3736
|
+
- configured logical geometry when requested;
|
|
3737
|
+
- face and shadow;
|
|
3738
|
+
- track and zones;
|
|
3739
|
+
- ticks;
|
|
3740
|
+
- labels;
|
|
3741
|
+
- pointer or indicator travel;
|
|
3742
|
+
- zero marker;
|
|
3743
|
+
- rolling-tape window;
|
|
3744
|
+
- readout text estimates;
|
|
3745
|
+
- light hardware, halo, and blur.
|
|
3746
|
+
|
|
3747
|
+
The renderer may union deterministic bounds with `getBBox()` when available.
|
|
3748
|
+
|
|
3749
|
+
When adding a visual extent:
|
|
3750
|
+
|
|
3751
|
+
1. identify every position it may occupy;
|
|
3752
|
+
2. include stroke width, blur, cap, shadow, and animation variation;
|
|
3753
|
+
3. update deterministic bounds;
|
|
3754
|
+
4. test finite responsive layout;
|
|
3755
|
+
5. verify no clipping in intrinsic and contain modes.
|
|
3756
|
+
|
|
3757
|
+
Do not rely solely on browser `getBBox()` because Node tests and some environments do
|
|
3758
|
+
not provide it.
|
|
3759
|
+
|
|
3760
|
+
## Face clipping
|
|
3761
|
+
|
|
3762
|
+
A face with `clipContent: true` defines a reusable inner-bezel clip path. Subsequent
|
|
3763
|
+
groups inherit it through the renderer.
|
|
3764
|
+
|
|
3765
|
+
Content-fitted faces calculate their size from conservative content bounds before the
|
|
3766
|
+
clip is applied.
|
|
3767
|
+
|
|
3768
|
+
When adding a new custom layer to a clipped type:
|
|
3769
|
+
|
|
3770
|
+
- create it with `renderer.group()`;
|
|
3771
|
+
- place it after the face layer;
|
|
3772
|
+
- do not let intentionally clipped geometry inflate document layout beyond the face.
|
|
3773
|
+
|
|
3774
|
+
## Accessibility
|
|
3775
|
+
|
|
3776
|
+
The generated root is a meter:
|
|
3777
|
+
|
|
3778
|
+
```text
|
|
3779
|
+
role="meter"
|
|
3780
|
+
aria-label
|
|
3781
|
+
aria-valuemin
|
|
3782
|
+
aria-valuemax
|
|
3783
|
+
aria-valuenow
|
|
3784
|
+
aria-valuetext, when configured
|
|
3785
|
+
```
|
|
3786
|
+
|
|
3787
|
+
The SVG also receives title and optional description nodes.
|
|
3788
|
+
|
|
3789
|
+
New meaningful visual state must be represented by existing value semantics or an
|
|
3790
|
+
additional accessible description. Decorative layers should be `aria-hidden`.
|
|
3791
|
+
|
|
3792
|
+
Do not use visual jitter, flicker, or animation as the only carrier of information.
|
|
3793
|
+
|
|
3794
|
+
## DOM events
|
|
3795
|
+
|
|
3796
|
+
Events emitted from the host:
|
|
3797
|
+
|
|
3798
|
+
```text
|
|
3799
|
+
gaugeit:render
|
|
3800
|
+
gaugeit:change
|
|
3801
|
+
gaugeit:displaychange
|
|
3802
|
+
gaugeit:animationstart
|
|
3803
|
+
gaugeit:animationend
|
|
3804
|
+
gaugeit:settled
|
|
3805
|
+
gaugeit:typechange
|
|
3806
|
+
gaugeit:pause
|
|
3807
|
+
gaugeit:resume
|
|
3808
|
+
gaugeit:destroy
|
|
3809
|
+
```
|
|
3810
|
+
|
|
3811
|
+
Keep event names namespaced. When changing detail payloads, consider compatibility and
|
|
3812
|
+
update declarations/docs/tests where applicable.
|
|
3813
|
+
|
|
3814
|
+
## Auto mounting and managed instances
|
|
3815
|
+
|
|
3816
|
+
`createGauge()` and `autoMount()` use managed instance ownership. A new managed gauge
|
|
3817
|
+
on the same host destroys the old one. Repeated auto-mounting returns the existing
|
|
3818
|
+
instance.
|
|
3819
|
+
|
|
3820
|
+
Keep the WeakMap lifecycle safe:
|
|
3821
|
+
|
|
3822
|
+
- a destroyed instance must remove itself;
|
|
3823
|
+
- an old replaced instance must not remove a newer mapping;
|
|
3824
|
+
- custom element disconnection destroys its instance.
|
|
3825
|
+
|
|
3826
|
+
## React adapter boundary
|
|
3827
|
+
|
|
3828
|
+
The React adapter:
|
|
3829
|
+
|
|
3830
|
+
- imports the ESM distribution;
|
|
3831
|
+
- creates one gauge on mount;
|
|
3832
|
+
- replaces options when prop identity changes;
|
|
3833
|
+
- applies explicit value changes separately;
|
|
3834
|
+
- forwards the live instance;
|
|
3835
|
+
- destroys on unmount.
|
|
3836
|
+
|
|
3837
|
+
The core must never import React.
|
|
3838
|
+
|
|
3839
|
+
When changing adapter behavior:
|
|
3840
|
+
|
|
3841
|
+
- preserve browser-ready ESM output;
|
|
3842
|
+
- update `adapters/react/Gaugeit.d.ts`;
|
|
3843
|
+
- test controlled values and forwarded ref;
|
|
3844
|
+
- avoid stale-instance cleanup races.
|
|
3845
|
+
|
|
3846
|
+
## Composer boundary
|
|
3847
|
+
|
|
3848
|
+
The Composer package distributes prebuilt browser assets and one PHP command-line
|
|
3849
|
+
installer.
|
|
3850
|
+
|
|
3851
|
+
The installer:
|
|
3852
|
+
|
|
3853
|
+
- supports default, standalone, and all modes;
|
|
3854
|
+
- creates destination directories;
|
|
3855
|
+
- handles spaces in paths;
|
|
3856
|
+
- replaces assets atomically through temporary files;
|
|
3857
|
+
- cleans only known assets;
|
|
3858
|
+
- preserves unrelated files;
|
|
3859
|
+
- returns meaningful exit codes.
|
|
3860
|
+
|
|
3861
|
+
Do not add automatic public-directory writes to dependency installation.
|
|
3862
|
+
|
|
3863
|
+
## Adding a new option
|
|
3864
|
+
|
|
3865
|
+
Use this checklist:
|
|
3866
|
+
|
|
3867
|
+
1. Add the base default in `src/core/defaults.js` unless it is type-only.
|
|
3868
|
+
2. Add validation and normalization in `src/core/options.js`.
|
|
3869
|
+
3. Add TypeScript declaration in `src/index.d.ts`.
|
|
3870
|
+
4. Consume the resolved value in the correct layer, geometry, renderer, or core.
|
|
3871
|
+
5. Update bounds if visual extents change.
|
|
3872
|
+
6. Update CSS if the option controls styling.
|
|
3873
|
+
7. Add focused option tests.
|
|
3874
|
+
8. Add rendering/lifecycle tests.
|
|
3875
|
+
9. Update `docs/api.md`, README examples, and `docs/ai/options-reference.md`.
|
|
3876
|
+
10. Rebuild all distributions.
|
|
3877
|
+
11. Run `npm run check`.
|
|
3878
|
+
12. Verify clean reproducibility.
|
|
3879
|
+
|
|
3880
|
+
For compatibility, use explicit aliases only when preserving an established public
|
|
3881
|
+
contract. Do not create undocumented aliases for convenience.
|
|
3882
|
+
|
|
3883
|
+
## Adding a new layer
|
|
3884
|
+
|
|
3885
|
+
1. Decide whether the layer is static or dynamic.
|
|
3886
|
+
2. Create `src/layers/<name>.js`.
|
|
3887
|
+
3. Use `renderer.group()`.
|
|
3888
|
+
4. Use `svgElement()` or `createElementNS`; never `innerHTML`.
|
|
3889
|
+
5. Return a controller only if dynamic state exists.
|
|
3890
|
+
6. Clean all owned resources in controller `destroy()`.
|
|
3891
|
+
7. Add pure geometry helpers if calculations can be DOM-independent.
|
|
3892
|
+
8. Add bounds support.
|
|
3893
|
+
9. Export through `src/index.js` if intended for custom types.
|
|
3894
|
+
10. Add declarations when public.
|
|
3895
|
+
11. Insert into type arrays at the intended paint position.
|
|
3896
|
+
12. Add unit and DOM tests.
|
|
3897
|
+
13. Document usage.
|
|
3898
|
+
|
|
3899
|
+
## Adding a new gauge type
|
|
3900
|
+
|
|
3901
|
+
1. Reuse an existing geometry family if possible.
|
|
3902
|
+
2. Express visual identity in defaults.
|
|
3903
|
+
3. Add a `configure()` hook only for derived fallback geometry.
|
|
3904
|
+
4. Select layers in exact paint order.
|
|
3905
|
+
5. Include the shared light layer if normal annunciator support is desired.
|
|
3906
|
+
6. Keep all colors and principal dimensions overridable.
|
|
3907
|
+
7. Register and expose the type.
|
|
3908
|
+
8. Test default rendering, option override, finite bounds, and distribution behavior.
|
|
3909
|
+
9. Add screenshot/example/documentation.
|
|
3910
|
+
10. Rebuild and validate.
|
|
3911
|
+
|
|
3912
|
+
## TypeScript declarations
|
|
3913
|
+
|
|
3914
|
+
`src/index.d.ts` is canonical for the core package API.
|
|
3915
|
+
`adapters/react/Gaugeit.d.ts` is canonical for React props and the forwarded ref.
|
|
3916
|
+
|
|
3917
|
+
Update `src/index.d.ts` when changing:
|
|
3918
|
+
|
|
3919
|
+
- option keys;
|
|
3920
|
+
- accepted enum values;
|
|
3921
|
+
- public methods;
|
|
3922
|
+
- event-facing types if added;
|
|
3923
|
+
- layer contexts;
|
|
3924
|
+
- geometry exports;
|
|
3925
|
+
- registry definitions.
|
|
3926
|
+
|
|
3927
|
+
Update `adapters/react/Gaugeit.d.ts` when changing React props or the forwarded ref
|
|
3928
|
+
type.
|
|
3929
|
+
|
|
3930
|
+
Run:
|
|
3931
|
+
|
|
3932
|
+
```bash
|
|
3933
|
+
npm run typecheck
|
|
3934
|
+
```
|
|
3935
|
+
|
|
3936
|
+
The build copies declarations into `dist/gaugeit.d.ts`. Do not edit that copy.
|
|
3937
|
+
|
|
3938
|
+
## Test selection
|
|
3939
|
+
|
|
3940
|
+
Use focused tests while iterating.
|
|
3941
|
+
|
|
3942
|
+
| Change | Likely tests |
|
|
3943
|
+
| --- | --- |
|
|
3944
|
+
| defaults or normalization | `tests/options.test.js` |
|
|
3945
|
+
| radial geometry | `tests/geometry.test.js`, `tests/zones.test.js` |
|
|
3946
|
+
| linear behavior | `tests/linear.test.js`, `tests/advanced-types.test.js` |
|
|
3947
|
+
| light behavior | `tests/light.test.js`, `tests/advanced-types.test.js` |
|
|
3948
|
+
| lifecycle | `tests/lifecycle.test.js` |
|
|
3949
|
+
| registry/type exposure | `tests/registry.test.js` |
|
|
3950
|
+
| CSS hooks | `tests/css.test.js` |
|
|
3951
|
+
| output inventory/build | `tests/distribution.test.js` |
|
|
3952
|
+
| npm/Composer metadata | `tests/package-metadata.test.js` |
|
|
3953
|
+
| React adapter | `tests/react-adapter.test.js` |
|
|
3954
|
+
| examples | `tests/example-page.test.js`, `tests/framework-examples.test.js` |
|
|
3955
|
+
| repository rules | `tests/repository-hygiene.test.js` |
|
|
3956
|
+
|
|
3957
|
+
The main Node test command includes `tests/*.test.js` with concurrency one:
|
|
3958
|
+
|
|
3959
|
+
```bash
|
|
3960
|
+
npm test
|
|
3961
|
+
```
|
|
3962
|
+
|
|
3963
|
+
DOM smoke:
|
|
3964
|
+
|
|
3965
|
+
```bash
|
|
3966
|
+
npm run test:browser
|
|
3967
|
+
```
|
|
3968
|
+
|
|
3969
|
+
Optional real Chromium smoke:
|
|
3970
|
+
|
|
3971
|
+
```bash
|
|
3972
|
+
npm run test:chromium
|
|
3973
|
+
```
|
|
3974
|
+
|
|
3975
|
+
## Build output
|
|
3976
|
+
|
|
3977
|
+
Run:
|
|
3978
|
+
|
|
3979
|
+
```bash
|
|
3980
|
+
npm run clean
|
|
3981
|
+
npm run build
|
|
3982
|
+
```
|
|
3983
|
+
|
|
3984
|
+
or use the full:
|
|
3985
|
+
|
|
3986
|
+
```bash
|
|
3987
|
+
npm run check
|
|
3988
|
+
```
|
|
3989
|
+
|
|
3990
|
+
The build must remain deterministic:
|
|
3991
|
+
|
|
3992
|
+
- stable file ordering;
|
|
3993
|
+
- no timestamps in generated content;
|
|
3994
|
+
- no random output;
|
|
3995
|
+
- normalized LF inputs;
|
|
3996
|
+
- no absolute local paths;
|
|
3997
|
+
- same source-map contents on Linux and Windows;
|
|
3998
|
+
- manifest sizes matching files.
|
|
3999
|
+
|
|
4000
|
+
CI performs:
|
|
4001
|
+
|
|
4002
|
+
```bash
|
|
4003
|
+
npm ci
|
|
4004
|
+
npm run check
|
|
4005
|
+
git diff --exit-code -- dist package-lock.json
|
|
4006
|
+
```
|
|
4007
|
+
|
|
4008
|
+
A build that passes locally but changes generated source maps on Linux is not complete.
|
|
4009
|
+
|
|
4010
|
+
## Documentation changes
|
|
4011
|
+
|
|
4012
|
+
Update only the smallest set of authoritative documents, but do not leave contradictory
|
|
4013
|
+
copies.
|
|
4014
|
+
|
|
4015
|
+
When changing public options:
|
|
4016
|
+
|
|
4017
|
+
```text
|
|
4018
|
+
README.md
|
|
4019
|
+
docs/api.md
|
|
4020
|
+
src/index.d.ts
|
|
4021
|
+
docs/ai/options-reference.md
|
|
4022
|
+
relevant recipes and troubleshooting
|
|
4023
|
+
```
|
|
4024
|
+
|
|
4025
|
+
When changing architecture:
|
|
4026
|
+
|
|
4027
|
+
```text
|
|
4028
|
+
docs/architecture.md
|
|
4029
|
+
docs/creating-gauge-types.md
|
|
4030
|
+
docs/ai/contributor-guide.md
|
|
4031
|
+
AGENTS.md, if repository rules changed
|
|
4032
|
+
```
|
|
4033
|
+
|
|
4034
|
+
`docs/ai/ai-full.md` is the single-file context bundle and must be regenerated or
|
|
4035
|
+
updated whenever its component AI guides change.
|
|
4036
|
+
|
|
4037
|
+
## Release-aware changes
|
|
4038
|
+
|
|
4039
|
+
Do not rewrite an already published npm version. A released version is immutable.
|
|
4040
|
+
|
|
4041
|
+
For a new release:
|
|
4042
|
+
|
|
4043
|
+
- update package version and public version constant consistently;
|
|
4044
|
+
- update changelog;
|
|
4045
|
+
- update exact CDN examples;
|
|
4046
|
+
- rebuild `dist/`;
|
|
4047
|
+
- run all validation;
|
|
4048
|
+
- create and push the Git tag only after green CI;
|
|
4049
|
+
- publish npm;
|
|
4050
|
+
- update Packagist from the Git tag.
|
|
4051
|
+
|
|
4052
|
+
Release mechanics remain explicit maintainer actions. CI validates but does not publish.
|
|
4053
|
+
|
|
4054
|
+
## Definition of done for contributors
|
|
4055
|
+
|
|
4056
|
+
A coding agent may say a task is complete only when:
|
|
4057
|
+
|
|
4058
|
+
- behavior is implemented in canonical source;
|
|
4059
|
+
- public compatibility is preserved or intentionally documented;
|
|
4060
|
+
- focused tests and regression tests pass;
|
|
4061
|
+
- full `npm run check` passes;
|
|
4062
|
+
- declarations match runtime;
|
|
4063
|
+
- docs and examples match behavior;
|
|
4064
|
+
- visual bounds cover new extents;
|
|
4065
|
+
- cleanup covers all new resources;
|
|
4066
|
+
- generated distribution is rebuilt;
|
|
4067
|
+
- reproducibility diff is clean;
|
|
4068
|
+
- no accidental files or unrelated edits are present.
|
|
4069
|
+
|
|
4070
|
+
---
|
|
4071
|
+
|
|
4072
|
+
# Part V — Troubleshooting
|
|
4073
|
+
|
|
4074
|
+
This guide diagnoses common Gaugeit.js 0.1.1 integration, rendering, lifecycle,
|
|
4075
|
+
packaging, and contribution failures. Start from the observed symptom. Do not guess at
|
|
4076
|
+
new API names to work around a configuration mistake.
|
|
4077
|
+
|
|
4078
|
+
## Gauge does not appear
|
|
4079
|
+
|
|
4080
|
+
### Check that the host exists
|
|
4081
|
+
|
|
4082
|
+
This fails when creation runs before the element exists:
|
|
4083
|
+
|
|
4084
|
+
```js
|
|
4085
|
+
Gaugeit.createGauge('#gauge', {
|
|
4086
|
+
value: 50
|
|
4087
|
+
});
|
|
4088
|
+
```
|
|
4089
|
+
|
|
4090
|
+
Verify:
|
|
4091
|
+
|
|
4092
|
+
```js
|
|
4093
|
+
const host = document.querySelector('#gauge');
|
|
4094
|
+
|
|
4095
|
+
if (!host) {
|
|
4096
|
+
throw new Error('Gauge host was not found.');
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
const gauge = Gaugeit.createGauge(host, {
|
|
4100
|
+
value: 50
|
|
4101
|
+
});
|
|
4102
|
+
```
|
|
4103
|
+
|
|
4104
|
+
Place the script after the host, use `defer`, use a module script, or wait for
|
|
4105
|
+
`DOMContentLoaded`.
|
|
4106
|
+
|
|
4107
|
+
### Check the selector
|
|
4108
|
+
|
|
4109
|
+
`createGauge()` accepts an element or valid CSS selector. A selector typo throws a
|
|
4110
|
+
target-not-found error. Do not pass a React component, jQuery wrapper, NodeList, or
|
|
4111
|
+
plain object.
|
|
4112
|
+
|
|
4113
|
+
### Check host layout
|
|
4114
|
+
|
|
4115
|
+
The SVG is responsive to the host. Verify that the host and its ancestors are not
|
|
4116
|
+
collapsed, hidden by CSS, or assigned zero width/height.
|
|
4117
|
+
|
|
4118
|
+
For contain mode, provide both dimensions:
|
|
4119
|
+
|
|
4120
|
+
```css
|
|
4121
|
+
#gauge {
|
|
4122
|
+
width: 320px;
|
|
4123
|
+
height: 220px;
|
|
4124
|
+
}
|
|
4125
|
+
```
|
|
4126
|
+
|
|
4127
|
+
```js
|
|
4128
|
+
layout: {
|
|
4129
|
+
mode: 'contain'
|
|
4130
|
+
}
|
|
4131
|
+
```
|
|
4132
|
+
|
|
4133
|
+
### Check CSS
|
|
4134
|
+
|
|
4135
|
+
UMD, ESM, CommonJS, React, custom element, and Composer two-file usage require Gaugeit
|
|
4136
|
+
CSS.
|
|
4137
|
+
|
|
4138
|
+
Browser:
|
|
4139
|
+
|
|
4140
|
+
```html
|
|
4141
|
+
<link rel="stylesheet" href="./dist/gaugeit.min.css">
|
|
4142
|
+
```
|
|
4143
|
+
|
|
4144
|
+
npm:
|
|
4145
|
+
|
|
4146
|
+
```js
|
|
4147
|
+
import 'gaugeit.js/css';
|
|
4148
|
+
```
|
|
4149
|
+
|
|
4150
|
+
Standalone already injects CSS. Missing CSS may produce incorrect sizing or animation
|
|
4151
|
+
behavior even when SVG nodes exist.
|
|
4152
|
+
|
|
4153
|
+
## `Gaugeit is not defined`
|
|
4154
|
+
|
|
4155
|
+
Possible causes:
|
|
4156
|
+
|
|
4157
|
+
- UMD/standalone script failed to load;
|
|
4158
|
+
- application script executes before the distribution;
|
|
4159
|
+
- wrong global spelling;
|
|
4160
|
+
- ESM file was loaded as a classic script;
|
|
4161
|
+
- a CDN URL is wrong.
|
|
4162
|
+
|
|
4163
|
+
Correct UMD order:
|
|
4164
|
+
|
|
4165
|
+
```html
|
|
4166
|
+
<script src="./dist/gaugeit.umd.min.js"></script>
|
|
4167
|
+
<script>
|
|
4168
|
+
Gaugeit.createGauge('#gauge', {
|
|
4169
|
+
value: 50
|
|
4170
|
+
});
|
|
4171
|
+
</script>
|
|
4172
|
+
```
|
|
4173
|
+
|
|
4174
|
+
The global is exactly:
|
|
4175
|
+
|
|
4176
|
+
```text
|
|
4177
|
+
Gaugeit
|
|
4178
|
+
```
|
|
4179
|
+
|
|
4180
|
+
Not:
|
|
4181
|
+
|
|
4182
|
+
```text
|
|
4183
|
+
GaugeIt
|
|
4184
|
+
GaugeIT
|
|
4185
|
+
gaugeit
|
|
4186
|
+
```
|
|
4187
|
+
|
|
4188
|
+
## `Gaugeit` versus `GaugeIt` filename or symbol confusion
|
|
4189
|
+
|
|
4190
|
+
Project files and exports intentionally use:
|
|
4191
|
+
|
|
4192
|
+
```text
|
|
4193
|
+
Gaugeit
|
|
4194
|
+
```
|
|
4195
|
+
|
|
4196
|
+
Examples:
|
|
4197
|
+
|
|
4198
|
+
```text
|
|
4199
|
+
adapters/react/Gaugeit.js
|
|
4200
|
+
import Gaugeit from 'gaugeit.js/react'
|
|
4201
|
+
window.Gaugeit
|
|
4202
|
+
```
|
|
4203
|
+
|
|
4204
|
+
Windows may hide case-only filename mistakes that fail on Linux CI. Preserve exact
|
|
4205
|
+
case in imports and package exports.
|
|
4206
|
+
|
|
4207
|
+
## ESM import fails in a browser
|
|
4208
|
+
|
|
4209
|
+
This bare import does not work in a normal browser without a resolver:
|
|
4210
|
+
|
|
4211
|
+
```js
|
|
4212
|
+
import { createGauge } from 'gaugeit.js';
|
|
4213
|
+
```
|
|
4214
|
+
|
|
4215
|
+
Use one of:
|
|
4216
|
+
|
|
4217
|
+
- a bundler;
|
|
4218
|
+
- an import map;
|
|
4219
|
+
- a server resolver;
|
|
4220
|
+
- an explicit browser path:
|
|
4221
|
+
|
|
4222
|
+
```js
|
|
4223
|
+
import { createGauge } from './dist/gaugeit.esm.js';
|
|
4224
|
+
```
|
|
4225
|
+
|
|
4226
|
+
Load the file as a module:
|
|
4227
|
+
|
|
4228
|
+
```html
|
|
4229
|
+
<script type="module" src="./app.js"></script>
|
|
4230
|
+
```
|
|
4231
|
+
|
|
4232
|
+
Do not load ESM as a classic script.
|
|
4233
|
+
|
|
4234
|
+
## CommonJS works but gauge is unstyled
|
|
4235
|
+
|
|
4236
|
+
The CommonJS entry resolves JavaScript only:
|
|
4237
|
+
|
|
4238
|
+
```js
|
|
4239
|
+
const { createGauge } = require('gaugeit.js');
|
|
4240
|
+
```
|
|
4241
|
+
|
|
4242
|
+
Add the CSS to the application bundle or page. The `require()` call does not inject it.
|
|
4243
|
+
|
|
4244
|
+
## Standalone and separate CSS are both loaded
|
|
4245
|
+
|
|
4246
|
+
This is usually unnecessary. Standalone injects the canonical stylesheet. Loading
|
|
4247
|
+
separate Gaugeit CSS as well can make debugging overrides harder.
|
|
4248
|
+
|
|
4249
|
+
Choose either:
|
|
4250
|
+
|
|
4251
|
+
```text
|
|
4252
|
+
UMD + CSS
|
|
4253
|
+
```
|
|
4254
|
+
|
|
4255
|
+
or:
|
|
4256
|
+
|
|
4257
|
+
```text
|
|
4258
|
+
standalone JavaScript
|
|
4259
|
+
```
|
|
4260
|
+
|
|
4261
|
+
unless duplicate inclusion is intentional and understood.
|
|
4262
|
+
|
|
4263
|
+
## CDN installation changes unexpectedly
|
|
4264
|
+
|
|
4265
|
+
Use an exact version:
|
|
4266
|
+
|
|
4267
|
+
```text
|
|
4268
|
+
gaugeit.js@0.1.1
|
|
4269
|
+
```
|
|
4270
|
+
|
|
4271
|
+
Do not use an unversioned URL in production. Verify the complete file path:
|
|
4272
|
+
|
|
4273
|
+
```text
|
|
4274
|
+
dist/gaugeit.umd.min.js
|
|
4275
|
+
dist/gaugeit.min.css
|
|
4276
|
+
dist/gaugeit.standalone.min.js
|
|
4277
|
+
```
|
|
4278
|
+
|
|
4279
|
+
CDN propagation may take a short time after npm publication. Check the npm package
|
|
4280
|
+
first when a newly published CDN file is missing.
|
|
4281
|
+
|
|
4282
|
+
## npm import path is rejected
|
|
4283
|
+
|
|
4284
|
+
Use package exports:
|
|
4285
|
+
|
|
4286
|
+
```js
|
|
4287
|
+
import { createGauge } from 'gaugeit.js';
|
|
4288
|
+
import 'gaugeit.js/css';
|
|
4289
|
+
import Gaugeit from 'gaugeit.js/react';
|
|
4290
|
+
import { defineGaugeitElement } from 'gaugeit.js/web-component';
|
|
4291
|
+
```
|
|
4292
|
+
|
|
4293
|
+
Do not import private repository paths such as:
|
|
4294
|
+
|
|
4295
|
+
```js
|
|
4296
|
+
import { createGauge } from 'gaugeit.js/src/index.js';
|
|
4297
|
+
```
|
|
4298
|
+
|
|
4299
|
+
Package export maps intentionally limit supported subpaths.
|
|
4300
|
+
|
|
4301
|
+
## React gauge keeps rebuilding
|
|
4302
|
+
|
|
4303
|
+
The React adapter replaces the complete options object when its identity changes.
|
|
4304
|
+
This creates a new object every render:
|
|
4305
|
+
|
|
4306
|
+
```jsx
|
|
4307
|
+
<Gaugeit options={{ type: 'arc', min: 0, max: 100 }} />
|
|
4308
|
+
```
|
|
4309
|
+
|
|
4310
|
+
Prefer:
|
|
4311
|
+
|
|
4312
|
+
```jsx
|
|
4313
|
+
const options = useMemo(() => ({
|
|
4314
|
+
type: 'arc',
|
|
4315
|
+
min: 0,
|
|
4316
|
+
max: 100
|
|
4317
|
+
}), []);
|
|
4318
|
+
|
|
4319
|
+
return <Gaugeit options={options} />;
|
|
4320
|
+
```
|
|
4321
|
+
|
|
4322
|
+
A changing options object is valid when the configuration truly changes. Stabilization
|
|
4323
|
+
is a performance and lifecycle decision, not a requirement for correctness.
|
|
4324
|
+
|
|
4325
|
+
## React value does not match `options.value`
|
|
4326
|
+
|
|
4327
|
+
An explicit `value` prop is authoritative:
|
|
4328
|
+
|
|
4329
|
+
```jsx
|
|
4330
|
+
<Gaugeit
|
|
4331
|
+
value={72}
|
|
4332
|
+
options={{
|
|
4333
|
+
value: 10
|
|
4334
|
+
}}
|
|
4335
|
+
/>
|
|
4336
|
+
```
|
|
4337
|
+
|
|
4338
|
+
The displayed target is 72. Avoid duplicating value in both places.
|
|
4339
|
+
|
|
4340
|
+
## React ref is null
|
|
4341
|
+
|
|
4342
|
+
The forwarded ref becomes the live instance after mount. Do not read it during initial
|
|
4343
|
+
render. Use it from effects or event handlers:
|
|
4344
|
+
|
|
4345
|
+
```jsx
|
|
4346
|
+
const gaugeRef = useRef(null);
|
|
4347
|
+
|
|
4348
|
+
function update() {
|
|
4349
|
+
gaugeRef.current?.setValue(80);
|
|
4350
|
+
}
|
|
4351
|
+
```
|
|
4352
|
+
|
|
4353
|
+
## Gauge updates inefficiently
|
|
4354
|
+
|
|
4355
|
+
For numeric samples, use:
|
|
4356
|
+
|
|
4357
|
+
```js
|
|
4358
|
+
gauge.setValue(nextValue);
|
|
4359
|
+
```
|
|
4360
|
+
|
|
4361
|
+
Do not repeatedly do:
|
|
4362
|
+
|
|
4363
|
+
```js
|
|
4364
|
+
gauge.updateOptions({
|
|
4365
|
+
value: nextValue
|
|
4366
|
+
});
|
|
4367
|
+
```
|
|
4368
|
+
|
|
4369
|
+
unless static configuration also needs rebuilding.
|
|
4370
|
+
|
|
4371
|
+
## `updateOptions()` leaves old styling behind
|
|
4372
|
+
|
|
4373
|
+
`updateOptions()` deep-merges a patch. Omitted keys remain from earlier user options.
|
|
4374
|
+
|
|
4375
|
+
Use `replaceOptions()` for a complete declarative mode switch:
|
|
4376
|
+
|
|
4377
|
+
```js
|
|
4378
|
+
gauge.replaceOptions({
|
|
4379
|
+
type: 'classic',
|
|
4380
|
+
value: 50
|
|
4381
|
+
});
|
|
4382
|
+
```
|
|
4383
|
+
|
|
4384
|
+
## Zone list does not append
|
|
4385
|
+
|
|
4386
|
+
Arrays are replaced, not concatenated:
|
|
4387
|
+
|
|
4388
|
+
```js
|
|
4389
|
+
gauge.updateOptions({
|
|
4390
|
+
zones: newZones
|
|
4391
|
+
});
|
|
4392
|
+
```
|
|
4393
|
+
|
|
4394
|
+
replaces the previous list. Build the complete desired array in application code.
|
|
4395
|
+
|
|
4396
|
+
## Transparent zone still shows a track
|
|
4397
|
+
|
|
4398
|
+
Check:
|
|
4399
|
+
|
|
4400
|
+
```js
|
|
4401
|
+
track: {
|
|
4402
|
+
showUnderZones: false
|
|
4403
|
+
}
|
|
4404
|
+
```
|
|
4405
|
+
|
|
4406
|
+
A transparent or missing zone color produces a true gap only when the base track is not
|
|
4407
|
+
painted under zones.
|
|
4408
|
+
|
|
4409
|
+
Accepted gap colors include:
|
|
4410
|
+
|
|
4411
|
+
```text
|
|
4412
|
+
transparent
|
|
4413
|
+
none
|
|
4414
|
+
empty or missing color
|
|
4415
|
+
```
|
|
4416
|
+
|
|
4417
|
+
## Overlapping zone color is unexpected
|
|
4418
|
+
|
|
4419
|
+
Later matching zones win. Reorder the zone list or make ranges non-overlapping.
|
|
4420
|
+
|
|
4421
|
+
This also affects color-by-zone pointer, indicator, and tape behavior.
|
|
4422
|
+
|
|
4423
|
+
## Pointer moves in the wrong direction
|
|
4424
|
+
|
|
4425
|
+
Use:
|
|
4426
|
+
|
|
4427
|
+
```js
|
|
4428
|
+
invert: true
|
|
4429
|
+
```
|
|
4430
|
+
|
|
4431
|
+
Do not reverse zones manually. Inversion changes visual mapping while numeric zones
|
|
4432
|
+
stay attached to their values.
|
|
4433
|
+
|
|
4434
|
+
Verify the configured `startAngle` and `endAngle` if a custom radial sweep is also
|
|
4435
|
+
present.
|
|
4436
|
+
|
|
4437
|
+
## Linear scale moves vertically in the wrong direction
|
|
4438
|
+
|
|
4439
|
+
Public linear Y coordinates increase upward from `linear.originY`.
|
|
4440
|
+
|
|
4441
|
+
Correct mental model:
|
|
4442
|
+
|
|
4443
|
+
```text
|
|
4444
|
+
positive public Y -> visually upward
|
|
4445
|
+
negative public Y -> visually downward
|
|
4446
|
+
```
|
|
4447
|
+
|
|
4448
|
+
Do not use raw SVG downward-positive assumptions in `startY`, `endY`,
|
|
4449
|
+
`labelOffset`, `zoneOffset`, readout Y, or `centerOffset`.
|
|
4450
|
+
|
|
4451
|
+
## Linear axis disappears or has invalid geometry
|
|
4452
|
+
|
|
4453
|
+
The start and end points must differ. Runtime repairs a zero-length axis to one unit,
|
|
4454
|
+
but the resulting visual is unlikely to match intent.
|
|
4455
|
+
|
|
4456
|
+
Check:
|
|
4457
|
+
|
|
4458
|
+
```js
|
|
4459
|
+
linear: {
|
|
4460
|
+
startX,
|
|
4461
|
+
startY,
|
|
4462
|
+
endX,
|
|
4463
|
+
endY
|
|
4464
|
+
}
|
|
4465
|
+
```
|
|
4466
|
+
|
|
4467
|
+
## Center-zero reference is not centered
|
|
4468
|
+
|
|
4469
|
+
Verify:
|
|
4470
|
+
|
|
4471
|
+
- range contains `centerZero.zeroValue`;
|
|
4472
|
+
- `symmetric: true` is enabled when equal magnitude is required;
|
|
4473
|
+
- selected type is `center-zero` or `classic-linear-zero`;
|
|
4474
|
+
- marker visibility and coordinates were not overridden incorrectly.
|
|
4475
|
+
|
|
4476
|
+
Example:
|
|
4477
|
+
|
|
4478
|
+
```js
|
|
4479
|
+
{
|
|
4480
|
+
type: 'center-zero',
|
|
4481
|
+
min: -100,
|
|
4482
|
+
max: 100,
|
|
4483
|
+
centerZero: {
|
|
4484
|
+
zeroValue: 0,
|
|
4485
|
+
symmetric: true
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
```
|
|
4489
|
+
|
|
4490
|
+
## Rolling tape behaves like the wrong instrument
|
|
4491
|
+
|
|
4492
|
+
Use exact type:
|
|
4493
|
+
|
|
4494
|
+
```js
|
|
4495
|
+
type: 'rolling-tape'
|
|
4496
|
+
```
|
|
4497
|
+
|
|
4498
|
+
The tape has a moving strip and fixed reference line. It does not use radial pointer
|
|
4499
|
+
options.
|
|
4500
|
+
|
|
4501
|
+
Adjust `tape.interval`, `minorSubdivisions`, and `majorSpacing`, not
|
|
4502
|
+
`pointer.length`.
|
|
4503
|
+
|
|
4504
|
+
## Readout is missing
|
|
4505
|
+
|
|
4506
|
+
This is insufficient:
|
|
4507
|
+
|
|
4508
|
+
```js
|
|
4509
|
+
readout: {
|
|
4510
|
+
visible: true
|
|
4511
|
+
}
|
|
4512
|
+
```
|
|
4513
|
+
|
|
4514
|
+
Enable each required child:
|
|
4515
|
+
|
|
4516
|
+
```js
|
|
4517
|
+
readout: {
|
|
4518
|
+
visible: true,
|
|
4519
|
+
title: {
|
|
4520
|
+
visible: true,
|
|
4521
|
+
text: 'PRESSURE'
|
|
4522
|
+
},
|
|
4523
|
+
value: {
|
|
4524
|
+
visible: true
|
|
4525
|
+
},
|
|
4526
|
+
unit: {
|
|
4527
|
+
visible: true,
|
|
4528
|
+
text: 'bar'
|
|
4529
|
+
}
|
|
4530
|
+
}
|
|
4531
|
+
```
|
|
4532
|
+
|
|
4533
|
+
## Readout automatic position is unexpected
|
|
4534
|
+
|
|
4535
|
+
`x: 'auto'` centers. `y: 'auto'` uses `position` and `margin`.
|
|
4536
|
+
|
|
4537
|
+
`offsetX` and `offsetY` refine automatic coordinates only. Numeric coordinates are
|
|
4538
|
+
final and do not inherit automatic offsets.
|
|
4539
|
+
|
|
4540
|
+
Type presets may supply different fallback coordinates. Read the selected type defaults
|
|
4541
|
+
when exact placement matters.
|
|
4542
|
+
|
|
4543
|
+
## Light does not turn on
|
|
4544
|
+
|
|
4545
|
+
Check trigger mode.
|
|
4546
|
+
|
|
4547
|
+
Manual:
|
|
4548
|
+
|
|
4549
|
+
```js
|
|
4550
|
+
light: {
|
|
4551
|
+
visible: true,
|
|
4552
|
+
on: true,
|
|
4553
|
+
trigger: {
|
|
4554
|
+
mode: 'manual'
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
```
|
|
4558
|
+
|
|
4559
|
+
Threshold:
|
|
4560
|
+
|
|
4561
|
+
```js
|
|
4562
|
+
light: {
|
|
4563
|
+
visible: true,
|
|
4564
|
+
trigger: {
|
|
4565
|
+
mode: 'above',
|
|
4566
|
+
value: 80
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
```
|
|
4570
|
+
|
|
4571
|
+
`on` is ignored by `below`, `above`, and `between`.
|
|
4572
|
+
|
|
4573
|
+
Also verify `visible: true`.
|
|
4574
|
+
|
|
4575
|
+
## Light does not pulse
|
|
4576
|
+
|
|
4577
|
+
Pulse is visible only while the light is active.
|
|
4578
|
+
|
|
4579
|
+
Check:
|
|
4580
|
+
|
|
4581
|
+
```js
|
|
4582
|
+
light: {
|
|
4583
|
+
visible: true,
|
|
4584
|
+
pulse: true,
|
|
4585
|
+
blink: false,
|
|
4586
|
+
trigger: {
|
|
4587
|
+
mode: 'manual'
|
|
4588
|
+
},
|
|
4589
|
+
on: true
|
|
4590
|
+
}
|
|
4591
|
+
```
|
|
4592
|
+
|
|
4593
|
+
Blink takes precedence over pulse. If both are true, the light blinks instead of
|
|
4594
|
+
pulsing.
|
|
4595
|
+
|
|
4596
|
+
Reduced-motion preference suppresses motion. Test system/browser accessibility
|
|
4597
|
+
settings before treating this as a rendering defect.
|
|
4598
|
+
|
|
4599
|
+
## Light does not blink
|
|
4600
|
+
|
|
4601
|
+
Verify:
|
|
4602
|
+
|
|
4603
|
+
- active trigger;
|
|
4604
|
+
- `blink: true`;
|
|
4605
|
+
- `blinkInterval >= 100`;
|
|
4606
|
+
- reduced-motion is not active.
|
|
4607
|
+
|
|
4608
|
+
The off half-cycle shows the physical unlit lens rather than making the whole lamp
|
|
4609
|
+
transparent.
|
|
4610
|
+
|
|
4611
|
+
## Light flicker is not visible
|
|
4612
|
+
|
|
4613
|
+
Verify:
|
|
4614
|
+
|
|
4615
|
+
```js
|
|
4616
|
+
flicker: true
|
|
4617
|
+
```
|
|
4618
|
+
|
|
4619
|
+
and at least one of:
|
|
4620
|
+
|
|
4621
|
+
```js
|
|
4622
|
+
flickerIntensity > 0
|
|
4623
|
+
flickerGlowRadius > 0
|
|
4624
|
+
```
|
|
4625
|
+
|
|
4626
|
+
Flicker runs only while active and is disabled under reduced motion.
|
|
4627
|
+
|
|
4628
|
+
The default effect is intentionally subtle.
|
|
4629
|
+
|
|
4630
|
+
## Glow size does not follow lamp size
|
|
4631
|
+
|
|
4632
|
+
In 0.1.1, `glowRadius` is independent:
|
|
4633
|
+
|
|
4634
|
+
```js
|
|
4635
|
+
light: {
|
|
4636
|
+
radius: 8,
|
|
4637
|
+
glowRadius: 24
|
|
4638
|
+
}
|
|
4639
|
+
```
|
|
4640
|
+
|
|
4641
|
+
Set:
|
|
4642
|
+
|
|
4643
|
+
```js
|
|
4644
|
+
glowRadius: null
|
|
4645
|
+
```
|
|
4646
|
+
|
|
4647
|
+
to derive `radius * 1.82`.
|
|
4648
|
+
|
|
4649
|
+
Changing only `radius` does not automatically change an explicit numeric
|
|
4650
|
+
`glowRadius`.
|
|
4651
|
+
|
|
4652
|
+
## Glow is clipped
|
|
4653
|
+
|
|
4654
|
+
This should not happen with normalized built-in options because bounds reserve halo
|
|
4655
|
+
and blur.
|
|
4656
|
+
|
|
4657
|
+
For repository changes, compare rendering math in `src/layers/light.js` with bounds in
|
|
4658
|
+
`src/geometry/bounds.js`. A new halo extent, blur formula, or flicker variation requires
|
|
4659
|
+
matching bounds changes and tests.
|
|
4660
|
+
|
|
4661
|
+
For application custom layers, add correct geometry within the instrument or create a
|
|
4662
|
+
type that provides enough layout padding/bounds support.
|
|
4663
|
+
|
|
4664
|
+
## Animation does not run
|
|
4665
|
+
|
|
4666
|
+
Check:
|
|
4667
|
+
|
|
4668
|
+
```js
|
|
4669
|
+
animation: {
|
|
4670
|
+
enabled: true,
|
|
4671
|
+
duration: 850
|
|
4672
|
+
}
|
|
4673
|
+
```
|
|
4674
|
+
|
|
4675
|
+
Per update, do not pass:
|
|
4676
|
+
|
|
4677
|
+
```js
|
|
4678
|
+
{ animate: false }
|
|
4679
|
+
```
|
|
4680
|
+
|
|
4681
|
+
Reduced-motion may suppress animation when `respectReducedMotion` is true.
|
|
4682
|
+
|
|
4683
|
+
No animation is visible when source and target values are identical.
|
|
4684
|
+
|
|
4685
|
+
## Startup animation begins from the wrong point
|
|
4686
|
+
|
|
4687
|
+
Configure:
|
|
4688
|
+
|
|
4689
|
+
```js
|
|
4690
|
+
animation: {
|
|
4691
|
+
animateOnInit: true,
|
|
4692
|
+
startFrom: 'min'
|
|
4693
|
+
}
|
|
4694
|
+
```
|
|
4695
|
+
|
|
4696
|
+
Allowed values:
|
|
4697
|
+
|
|
4698
|
+
```text
|
|
4699
|
+
min
|
|
4700
|
+
max
|
|
4701
|
+
value
|
|
4702
|
+
numeric value
|
|
4703
|
+
```
|
|
4704
|
+
|
|
4705
|
+
`startFrom: 'value'` intentionally starts at the target and therefore has no visible
|
|
4706
|
+
startup travel.
|
|
4707
|
+
|
|
4708
|
+
## Jitter changes application data
|
|
4709
|
+
|
|
4710
|
+
It should not. Verify application code is reading:
|
|
4711
|
+
|
|
4712
|
+
```js
|
|
4713
|
+
gauge.getValue()
|
|
4714
|
+
gauge.getDisplayedValue()
|
|
4715
|
+
```
|
|
4716
|
+
|
|
4717
|
+
and not parsing generated pointer transforms.
|
|
4718
|
+
|
|
4719
|
+
Jitter is visual-only. If readout or ARIA values jitter after a repository change, the
|
|
4720
|
+
dynamic layer is using `state.pointerValue` in the wrong place.
|
|
4721
|
+
|
|
4722
|
+
## Gauge continues work offscreen
|
|
4723
|
+
|
|
4724
|
+
Defaults are:
|
|
4725
|
+
|
|
4726
|
+
```js
|
|
4727
|
+
performance: {
|
|
4728
|
+
pauseWhenHidden: true,
|
|
4729
|
+
pauseWhenOffscreen: true
|
|
4730
|
+
}
|
|
4731
|
+
```
|
|
4732
|
+
|
|
4733
|
+
IntersectionObserver may be unavailable in some environments. Manual `pause()` remains
|
|
4734
|
+
available.
|
|
4735
|
+
|
|
4736
|
+
Repository tests should use controlled lifecycle stubs rather than assuming browser
|
|
4737
|
+
observer availability.
|
|
4738
|
+
|
|
4739
|
+
## Instance leaks after component removal
|
|
4740
|
+
|
|
4741
|
+
An application that creates a gauge manually must destroy it:
|
|
4742
|
+
|
|
4743
|
+
```js
|
|
4744
|
+
const gauge = Gaugeit.createGauge(host, options);
|
|
4745
|
+
|
|
4746
|
+
// component cleanup
|
|
4747
|
+
gauge.destroy();
|
|
4748
|
+
```
|
|
4749
|
+
|
|
4750
|
+
The React adapter and custom element already own cleanup.
|
|
4751
|
+
|
|
4752
|
+
Custom layers with timers or observers must return a controller with `destroy()`.
|
|
4753
|
+
|
|
4754
|
+
## Auto mounting creates duplicates
|
|
4755
|
+
|
|
4756
|
+
`Gaugeit.autoMount()` is duplicate-safe for managed elements. Duplicate SVG usually
|
|
4757
|
+
means application code also called `createGauge()` independently or cloned generated
|
|
4758
|
+
content.
|
|
4759
|
+
|
|
4760
|
+
Use:
|
|
4761
|
+
|
|
4762
|
+
```js
|
|
4763
|
+
Gaugeit.getMountedGauge(host)
|
|
4764
|
+
```
|
|
4765
|
+
|
|
4766
|
+
to inspect ownership.
|
|
4767
|
+
|
|
4768
|
+
## Custom element options do not update
|
|
4769
|
+
|
|
4770
|
+
Observed attributes:
|
|
4771
|
+
|
|
4772
|
+
```text
|
|
4773
|
+
value
|
|
4774
|
+
min
|
|
4775
|
+
max
|
|
4776
|
+
type
|
|
4777
|
+
options
|
|
4778
|
+
```
|
|
4779
|
+
|
|
4780
|
+
The `options` attribute must be valid JSON. Attribute changes replace the declarative
|
|
4781
|
+
option object.
|
|
4782
|
+
|
|
4783
|
+
Property assignment for value:
|
|
4784
|
+
|
|
4785
|
+
```js
|
|
4786
|
+
element.value = 72;
|
|
4787
|
+
```
|
|
4788
|
+
|
|
4789
|
+
## Laravel or Twig JSON parsing fails
|
|
4790
|
+
|
|
4791
|
+
Encode data as JSON and escape it for the HTML attribute context.
|
|
4792
|
+
|
|
4793
|
+
Blade:
|
|
4794
|
+
|
|
4795
|
+
```blade
|
|
4796
|
+
data-options='@json($options)'
|
|
4797
|
+
```
|
|
4798
|
+
|
|
4799
|
+
Twig:
|
|
4800
|
+
|
|
4801
|
+
```twig
|
|
4802
|
+
data-options="{{ options|json_encode|e('html_attr') }}"
|
|
4803
|
+
```
|
|
4804
|
+
|
|
4805
|
+
Plain PHP:
|
|
4806
|
+
|
|
4807
|
+
```php
|
|
4808
|
+
htmlspecialchars(json_encode($options, JSON_THROW_ON_ERROR), ENT_QUOTES, 'UTF-8')
|
|
4809
|
+
```
|
|
4810
|
+
|
|
4811
|
+
Do not render PHP array syntax into a browser JSON parser.
|
|
4812
|
+
|
|
4813
|
+
## Composer command is missing
|
|
4814
|
+
|
|
4815
|
+
Confirm installation:
|
|
4816
|
+
|
|
4817
|
+
```bash
|
|
4818
|
+
composer show kasperi/gaugeit-js
|
|
4819
|
+
```
|
|
4820
|
+
|
|
4821
|
+
Use the platform-appropriate vendor binary:
|
|
4822
|
+
|
|
4823
|
+
Unix-like:
|
|
4824
|
+
|
|
4825
|
+
```bash
|
|
4826
|
+
vendor/bin/gaugeit-install-assets public/assets/vendor/gaugeit
|
|
4827
|
+
```
|
|
4828
|
+
|
|
4829
|
+
Windows Composer normally exposes a callable wrapper under `vendor\bin`.
|
|
4830
|
+
|
|
4831
|
+
Verify PHP 8.1 or newer.
|
|
4832
|
+
|
|
4833
|
+
## Composer assets are installed but browser returns 404
|
|
4834
|
+
|
|
4835
|
+
The installer writes to the filesystem path you provide. The web server must expose
|
|
4836
|
+
that path.
|
|
4837
|
+
|
|
4838
|
+
Check:
|
|
4839
|
+
|
|
4840
|
+
- destination is under the actual public document root;
|
|
4841
|
+
- framework asset helper matches the destination;
|
|
4842
|
+
- deployment included generated public assets;
|
|
4843
|
+
- URL casing matches filesystem casing.
|
|
4844
|
+
|
|
4845
|
+
## `npm run test:chromium` fails with `spawn chromium ENOENT`
|
|
4846
|
+
|
|
4847
|
+
The optional test expects a globally available executable named `chromium`.
|
|
4848
|
+
|
|
4849
|
+
This does not mean the library test failed in a browser. It means the process could not
|
|
4850
|
+
start Chromium.
|
|
4851
|
+
|
|
4852
|
+
Options:
|
|
4853
|
+
|
|
4854
|
+
- install Chromium and add it to PATH;
|
|
4855
|
+
- adjust the local environment to expose the expected executable;
|
|
4856
|
+
- rely on `npm run check` for the portable required suite.
|
|
4857
|
+
|
|
4858
|
+
Do not weaken CI because an optional local executable is missing.
|
|
4859
|
+
|
|
4860
|
+
## Local `npm run check` passes but GitHub CI fails at reproducibility
|
|
4861
|
+
|
|
4862
|
+
Typical symptom:
|
|
4863
|
+
|
|
4864
|
+
```text
|
|
4865
|
+
git diff --exit-code -- dist package-lock.json
|
|
4866
|
+
```
|
|
4867
|
+
|
|
4868
|
+
shows source-map differences.
|
|
4869
|
+
|
|
4870
|
+
Check:
|
|
4871
|
+
|
|
4872
|
+
- source files use LF, not CRLF;
|
|
4873
|
+
- Git `core.autocrlf` did not rewrite canonical inputs;
|
|
4874
|
+
- generated files do not contain absolute paths;
|
|
4875
|
+
- file ordering is deterministic;
|
|
4876
|
+
- no timestamps or randomness enter build output;
|
|
4877
|
+
- local `dist/` was rebuilt after the final source edit.
|
|
4878
|
+
|
|
4879
|
+
Repository policy:
|
|
4880
|
+
|
|
4881
|
+
```text
|
|
4882
|
+
.editorconfig -> LF
|
|
4883
|
+
.gitattributes -> text eol=lf
|
|
4884
|
+
```
|
|
4885
|
+
|
|
4886
|
+
Run:
|
|
4887
|
+
|
|
4888
|
+
```bash
|
|
4889
|
+
npm run check
|
|
4890
|
+
git diff -- dist package-lock.json
|
|
4891
|
+
```
|
|
4892
|
+
|
|
4893
|
+
and inspect the generated changes before pushing. To reproduce CI's exit-code check
|
|
4894
|
+
locally, stage the intended generated artifacts first and then run:
|
|
4895
|
+
|
|
4896
|
+
```bash
|
|
4897
|
+
git diff --exit-code -- dist package-lock.json
|
|
4898
|
+
```
|
|
4899
|
+
|
|
4900
|
+
CI performs this check from a clean checkout, so any post-build diff there is
|
|
4901
|
+
unexpected.
|
|
4902
|
+
|
|
4903
|
+
## `dist/` changes after a documentation-only edit
|
|
4904
|
+
|
|
4905
|
+
Ordinary docs should not change runtime distribution. If `npm run check` changes
|
|
4906
|
+
`dist/`, either:
|
|
4907
|
+
|
|
4908
|
+
- canonical source had uncommitted changes;
|
|
4909
|
+
- previous generated output was stale;
|
|
4910
|
+
- line endings or build environment changed;
|
|
4911
|
+
- a build script includes documentation unexpectedly.
|
|
4912
|
+
|
|
4913
|
+
Inspect the diff before committing.
|
|
4914
|
+
|
|
4915
|
+
## Generated files were edited manually
|
|
4916
|
+
|
|
4917
|
+
Discard manual generated edits, change canonical source, and rebuild:
|
|
4918
|
+
|
|
4919
|
+
```bash
|
|
4920
|
+
git restore dist
|
|
4921
|
+
npm run build
|
|
4922
|
+
```
|
|
4923
|
+
|
|
4924
|
+
If canonical source changes are already present, do not restore those. The goal is to
|
|
4925
|
+
remove only hand edits from `dist/`.
|
|
4926
|
+
|
|
4927
|
+
## TypeScript declarations do not match runtime
|
|
4928
|
+
|
|
4929
|
+
Canonical core declarations are:
|
|
4930
|
+
|
|
4931
|
+
```text
|
|
4932
|
+
src/index.d.ts
|
|
4933
|
+
```
|
|
4934
|
+
|
|
4935
|
+
The React adapter has a separate canonical declaration:
|
|
4936
|
+
|
|
4937
|
+
```text
|
|
4938
|
+
adapters/react/Gaugeit.d.ts
|
|
4939
|
+
```
|
|
4940
|
+
|
|
4941
|
+
Generated core copy:
|
|
4942
|
+
|
|
4943
|
+
```text
|
|
4944
|
+
dist/gaugeit.d.ts
|
|
4945
|
+
```
|
|
4946
|
+
|
|
4947
|
+
Update the declaration that owns the affected API, then run:
|
|
4948
|
+
|
|
4949
|
+
```bash
|
|
4950
|
+
npm run typecheck
|
|
4951
|
+
npm run build
|
|
4952
|
+
```
|
|
4953
|
+
|
|
4954
|
+
and add package-usage tests when an export or option changes.
|
|
4955
|
+
|
|
4956
|
+
## Package dry run omits expected files
|
|
4957
|
+
|
|
4958
|
+
Check `package.json` `files` and `exports`.
|
|
4959
|
+
|
|
4960
|
+
Run:
|
|
4961
|
+
|
|
4962
|
+
```bash
|
|
4963
|
+
npm pack --dry-run
|
|
4964
|
+
```
|
|
4965
|
+
|
|
4966
|
+
AI documentation under `docs/` is included because the package publishes `docs`.
|
|
4967
|
+
Top-level files not listed in `files` may be absent from the npm tarball even when they
|
|
4968
|
+
exist in GitHub.
|
|
4969
|
+
|
|
4970
|
+
## npm publication returns 403 requiring 2FA
|
|
4971
|
+
|
|
4972
|
+
The package contents may be valid even though publication is rejected.
|
|
4973
|
+
|
|
4974
|
+
Use an npm account with publication 2FA or an appropriate granular token. A failed
|
|
4975
|
+
403 attempt does not publish the version.
|
|
4976
|
+
|
|
4977
|
+
After successful publication, verify:
|
|
4978
|
+
|
|
4979
|
+
```bash
|
|
4980
|
+
npm view gaugeit.js@0.1.1 name version
|
|
4981
|
+
npm view gaugeit.js dist-tags
|
|
4982
|
+
```
|
|
4983
|
+
|
|
4984
|
+
## Packagist does not show the release
|
|
4985
|
+
|
|
4986
|
+
Composer package versions come from Git tags.
|
|
4987
|
+
|
|
4988
|
+
Verify:
|
|
4989
|
+
|
|
4990
|
+
- GitHub repository is public and correct;
|
|
4991
|
+
- `composer.json` is valid;
|
|
4992
|
+
- tag exists remotely;
|
|
4993
|
+
- Packagist package points to the correct repository;
|
|
4994
|
+
- tag corresponds to the intended commit.
|
|
4995
|
+
|
|
4996
|
+
Do not add a hard-coded `version` field to `composer.json` merely to fix tag discovery.
|
|
4997
|
+
|
|
4998
|
+
## Custom type is not found
|
|
4999
|
+
|
|
5000
|
+
Register before instance creation:
|
|
5001
|
+
|
|
5002
|
+
```js
|
|
5003
|
+
Gaugeit.registerGaugeType('my-type', definition);
|
|
5004
|
+
Gaugeit.createGauge('#gauge', {
|
|
5005
|
+
type: 'my-type'
|
|
5006
|
+
});
|
|
5007
|
+
```
|
|
5008
|
+
|
|
5009
|
+
Verify non-empty `layers`. Every layer must expose `render(context)`.
|
|
5010
|
+
|
|
5011
|
+
## Custom layer is behind or above the wrong content
|
|
5012
|
+
|
|
5013
|
+
Layer array order is SVG paint order. Reorder layers.
|
|
5014
|
+
|
|
5015
|
+
For example, pointer above readout:
|
|
5016
|
+
|
|
5017
|
+
```js
|
|
5018
|
+
layers: [
|
|
5019
|
+
Gaugeit.layers.face,
|
|
5020
|
+
Gaugeit.layers.zones,
|
|
5021
|
+
Gaugeit.layers.ticks,
|
|
5022
|
+
Gaugeit.layers.labels,
|
|
5023
|
+
Gaugeit.layers.readout,
|
|
5024
|
+
Gaugeit.layers.pointer
|
|
5025
|
+
]
|
|
5026
|
+
```
|
|
5027
|
+
|
|
5028
|
+
Do not use numeric CSS z-index as a substitute.
|
|
5029
|
+
|
|
5030
|
+
## Custom layer ignores clipping
|
|
5031
|
+
|
|
5032
|
+
Create its group through:
|
|
5033
|
+
|
|
5034
|
+
```js
|
|
5035
|
+
renderer.group('my-layer')
|
|
5036
|
+
```
|
|
5037
|
+
|
|
5038
|
+
and place it after a face layer that establishes clipping. Appending directly to an
|
|
5039
|
+
unmanaged SVG node can bypass renderer ownership and clip-path application.
|
|
5040
|
+
|
|
5041
|
+
## Custom timer survives `destroy()`
|
|
5042
|
+
|
|
5043
|
+
Return cleanup:
|
|
5044
|
+
|
|
5045
|
+
```js
|
|
5046
|
+
return {
|
|
5047
|
+
update() {
|
|
5048
|
+
// ...
|
|
5049
|
+
},
|
|
5050
|
+
|
|
5051
|
+
destroy() {
|
|
5052
|
+
clearInterval(timer);
|
|
5053
|
+
}
|
|
5054
|
+
};
|
|
5055
|
+
```
|
|
5056
|
+
|
|
5057
|
+
Test destruction explicitly.
|
|
5058
|
+
|
|
5059
|
+
## A bug fix has no regression test
|
|
5060
|
+
|
|
5061
|
+
Add the smallest test that demonstrates the defect. Choose the nearest suite:
|
|
5062
|
+
|
|
5063
|
+
```text
|
|
5064
|
+
options.test.js
|
|
5065
|
+
geometry.test.js
|
|
5066
|
+
zones.test.js
|
|
5067
|
+
linear.test.js
|
|
5068
|
+
light.test.js
|
|
5069
|
+
lifecycle.test.js
|
|
5070
|
+
distribution.test.js
|
|
5071
|
+
react-adapter.test.js
|
|
5072
|
+
```
|
|
5073
|
+
|
|
5074
|
+
A bug fix is incomplete when only the implementation changed.
|
|
5075
|
+
|
|
5076
|
+
## Final diagnostic sequence
|
|
5077
|
+
|
|
5078
|
+
For application integration:
|
|
5079
|
+
|
|
5080
|
+
1. verify distribution and CSS;
|
|
5081
|
+
2. verify exact global/import name;
|
|
5082
|
+
3. verify host exists and has layout;
|
|
5083
|
+
4. reduce options to `{ type, min, max, value }`;
|
|
5084
|
+
5. add nested options back one section at a time;
|
|
5085
|
+
6. inspect host-scoped Gaugeit events;
|
|
5086
|
+
7. test destruction and recreation.
|
|
5087
|
+
|
|
5088
|
+
For repository changes:
|
|
5089
|
+
|
|
5090
|
+
1. inspect canonical source;
|
|
5091
|
+
2. run focused tests;
|
|
5092
|
+
3. update declarations and docs;
|
|
5093
|
+
4. run `npm run check`;
|
|
5094
|
+
5. run reproducibility diff;
|
|
5095
|
+
6. inspect `git diff` for accidental files.
|
|
5096
|
+
|
|
5097
|
+
---
|