@zumer/orbit-kit 0.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -316
- package/package.json +20 -8
- package/src/base.js +606 -41
- package/src/color.js +139 -0
- package/src/o-clock.js +106 -129
- package/src/o-color-wheel.js +322 -0
- package/src/o-compass.js +45 -49
- package/src/o-donut.js +67 -79
- package/src/o-gauge.js +51 -117
- package/src/o-instrument.js +116 -105
- package/src/o-knob.js +137 -129
- package/src/o-menu.js +271 -183
- package/src/o-meter.js +35 -89
- package/src/o-radar-chart.js +391 -0
- package/src/o-radar.js +80 -123
- package/src/o-range.js +509 -0
- package/src/o-rings.js +66 -77
- package/src/o-spinner.js +214 -0
- package/src/o-status.js +209 -0
- package/src/o-sunburst.js +569 -0
- package/src/o-time-picker.js +447 -0
- package/src/o-timer.js +240 -0
- package/src/o-wheel.js +436 -0
- package/src/orbit-kit.js +23 -5
package/src/o-radar.js
CHANGED
|
@@ -1,145 +1,102 @@
|
|
|
1
|
+
import { OrbitKitElement, define, injectStyleOnce, universe, finiteNumber, cssValue, fixed, normalizeRange, escapeHTML as e, withShared, FORMAT_ATTRS } from './base.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* <o-radar
|
|
3
|
-
*
|
|
4
|
-
* Built the idiomatic Orbit way: the range rings ARE orbits — each `.orbit-N`
|
|
5
|
-
* is a concentric circle, so we just style its border instead of stacking
|
|
6
|
-
* full-circle arcs. On top of that grid: radial cross-hair spokes (`.vector`),
|
|
7
|
-
* a rotating sweep wedge with a bright leading edge, and glowing blips placed
|
|
8
|
-
* on the orbit matching their distance and aimed with `--o-angle-composite`.
|
|
9
|
-
*
|
|
10
|
-
* .bigbang > .gravity-spot
|
|
11
|
-
* ├── .orbit-{2..outer} (styled border) — range rings
|
|
12
|
-
* ├── .orbit-{outer} fit-range range-360 > .vector × spokes
|
|
13
|
-
* ├── .orbit-{outer} (rotating) > <o-arc> wedge — the sweep
|
|
14
|
-
* ├── .orbit-{level} > .satellite (angled) — one per blip
|
|
15
|
-
* └── .orbit-0 > .satellite.at-center — hub + label
|
|
4
|
+
* <o-radar>: a scope with range rings, spokes, a rotating sweep and blips.
|
|
16
5
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
6
|
+
* .orbit-12 ok-radar-grid range rings (one element, any count)
|
|
7
|
+
* .orbit-0 > .vector × spokes rays from the center, one radius long
|
|
8
|
+
* .orbit-12 o-radar-sweep conic-gradient beam, rotating
|
|
9
|
+
* .orbit-12 > .satellite × targets blips: angle from --o-angle-composite,
|
|
10
|
+
* distance from --o-alignment
|
|
19
11
|
*
|
|
20
|
-
*
|
|
12
|
+
* Target updates touch only the blip layers, so the sweep never restarts.
|
|
21
13
|
*/
|
|
22
|
-
import { OrbitKitElement, define, injectStyleOnce } from './base.js';
|
|
23
|
-
|
|
24
14
|
const RADAR_CSS = `
|
|
25
|
-
|
|
26
|
-
@keyframes o-radar-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
o-radar { --ok-font-size: 6cqmin; }
|
|
16
|
+
@keyframes o-radar-sweep { to { rotate: 360deg; } }
|
|
17
|
+
@keyframes o-radar-ping { 0% { scale: .5; opacity: 1; } 70% { opacity: .45; } 100% { scale: 1.7; opacity: 0; } }
|
|
18
|
+
o-radar .ok-radar-grid {
|
|
19
|
+
--c: var(--ok-radar-color, #22d3ee);
|
|
20
|
+
background: repeating-radial-gradient(closest-side, transparent 0 calc(100% / var(--ok-radar-rings) - 1px),
|
|
21
|
+
color-mix(in srgb, var(--c) 22%, transparent) calc(100% / var(--ok-radar-rings) - 1px) calc(100% / var(--ok-radar-rings)));
|
|
22
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c) 35%, transparent);
|
|
23
|
+
}
|
|
24
|
+
o-radar .ok-radar-spoke { background: color-mix(in srgb, var(--ok-radar-color, #22d3ee) 14%, transparent); height: 1px; }
|
|
25
|
+
o-radar .o-radar-sweep {
|
|
26
|
+
--c: var(--ok-radar-color, #22d3ee);
|
|
27
|
+
background: conic-gradient(from 0deg, transparent 0deg,
|
|
28
|
+
color-mix(in srgb, var(--c) 5%, transparent) calc(var(--ok-radar-beam) / 2),
|
|
29
|
+
color-mix(in srgb, var(--c) 30%, transparent) calc(var(--ok-radar-beam) - 3deg),
|
|
30
|
+
color-mix(in srgb, var(--c) 85%, transparent) var(--ok-radar-beam), transparent var(--ok-radar-beam));
|
|
31
|
+
animation: o-radar-sweep var(--ok-radar-speed, var(--o-radar-speed, 4s)) linear infinite;
|
|
30
32
|
}
|
|
31
|
-
o-radar .
|
|
32
|
-
o-radar .o-radar-blip {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
border: 1px solid currentColor; animation: o-radar-ping 1.8s ease-out infinite;
|
|
33
|
+
o-radar .ok-radar-target > .satellite { color: var(--ok-radar-color, #22d3ee); }
|
|
34
|
+
o-radar .o-radar-blip {
|
|
35
|
+
position: relative; border-radius: 50%; width: max(5px, 3.5cqmin); aspect-ratio: 1;
|
|
36
|
+
background: currentColor; box-shadow: 0 0 10px currentColor;
|
|
36
37
|
}
|
|
38
|
+
o-radar .o-radar-blip::after { content: ""; position: absolute; inset: -4px; border-radius: 50%; border: 1px solid currentColor; animation: o-radar-ping 1.8s ease-out infinite; }
|
|
39
|
+
o-radar .ok-radar-center { display: flex; flex-direction: column; align-items: center; gap: .3em; color: var(--ok-radar-color, #22d3ee); }
|
|
40
|
+
o-radar .ok-radar-center::before { content: ""; width: max(4px, 2.4cqmin); aspect-ratio: 1; border-radius: 50%; background: currentColor; }
|
|
41
|
+
o-radar .ok-radar-label { font-weight: 800; white-space: nowrap; }
|
|
42
|
+
@media (prefers-reduced-motion: reduce) { o-radar .o-radar-sweep, o-radar .o-radar-blip::after { animation: none; } }
|
|
37
43
|
`;
|
|
38
44
|
|
|
39
45
|
export class OrbitRadar extends OrbitKitElement {
|
|
40
46
|
static get observedAttributes() {
|
|
41
|
-
return ['targets', 'rings', 'spokes', 'size', 'color', 'sweep', 'sweep-width', 'speed', 'label'];
|
|
47
|
+
return withShared(['targets', 'rings', 'spokes', 'size', 'color', 'accent', 'sweep', 'sweep-width', 'speed', 'label', 'min', 'max'], FORMAT_ATTRS);
|
|
42
48
|
}
|
|
43
|
-
|
|
44
49
|
readConfig() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
color: t.color || null
|
|
49
|
-
}));
|
|
50
|
+
// `dist` is read in min..max (0 to 1 by default): min at the center, max at the edge.
|
|
51
|
+
const r = normalizeRange({ min: this._num('min', 0), max: this._num('max', 1) });
|
|
52
|
+
const share = (d) => (r.span ? (d - r.min) / r.span : 0);
|
|
50
53
|
return {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
min: r.min, max: r.max,
|
|
55
|
+
targets: this._array('targets').map((t) => {
|
|
56
|
+
const raw = Math.min(r.max, Math.max(r.min, finiteNumber(t.dist, r.min + r.span / 2)));
|
|
57
|
+
return { angle: ((finiteNumber(t.angle) % 360) + 360) % 360, raw, dist: share(raw), color: cssValue(t.color), label: t.label == null ? null : String(t.label) };
|
|
58
|
+
}),
|
|
59
|
+
rings: Math.max(1, Math.min(60, Math.round(this._num('rings', 4)))),
|
|
60
|
+
spokes: Math.max(0, Math.min(360, Math.round(this._num('spokes', 8)))), size: this._size('size', '240px'),
|
|
61
|
+
color: this._color('color', this._color('accent', null)), sweep: this._str('sweep', 'true') !== 'false',
|
|
62
|
+
sweepWidth: Math.min(300, Math.max(10, this._num('sweep-width', 110))), speed: Math.max(.01, this._num('speed', 4)), label: this.getAttribute('label')
|
|
60
63
|
};
|
|
61
64
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return
|
|
65
|
+
structuralSignature({ targets, speed, label, color, min, max, ...structure }) { return JSON.stringify(structure); }
|
|
66
|
+
_target(t) {
|
|
67
|
+
return `<div class="orbit-12 ok-radar-target"><div class="satellite" style="--o-angle-composite:${fixed(t.angle - 90)}deg;--o-alignment:calc(var(--o-radius) * ${fixed(1 - t.dist, 4)});${t.color ? `color:${e(t.color)}` : ''}"><div class="capsule o-radar-blip"></div></div></div>`;
|
|
65
68
|
}
|
|
66
|
-
|
|
67
69
|
build(cfg) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return `<div class="orbit-${orbit}" style="border:1px solid ${this._rgba(C, alpha)};"></div>`;
|
|
79
|
-
}).join('');
|
|
80
|
-
|
|
81
|
-
// Cross-hair spokes — radial vectors, evenly distributed.
|
|
82
|
-
const spokes = cfg.spokes
|
|
83
|
-
? `<div class="orbit-${outer} fit-range range-360 from-0">
|
|
84
|
-
${Array.from({ length: cfg.spokes }, () =>
|
|
85
|
-
`<div class="vector" style="background:${this._rgba(C, 0.14)};height:1px;"></div>`).join('')}
|
|
86
|
-
</div>`
|
|
87
|
-
: '';
|
|
88
|
-
|
|
89
|
-
// Sweep — a real rotating beam: a conic-gradient disc painted on the outer
|
|
90
|
-
// orbit circle, fading from a transparent tail to a crisp bright leading
|
|
91
|
-
// edge over `sweepWidth` degrees. Smooth, not a blocky fan of arcs.
|
|
92
|
-
const w = cfg.sweepWidth;
|
|
93
|
-
const beam = `conic-gradient(from 0deg,`
|
|
94
|
-
+ ` ${this._rgba(C, 0)} 0deg,`
|
|
95
|
-
+ ` ${this._rgba(C, 0.05)} ${(w * 0.5).toFixed(1)}deg,`
|
|
96
|
-
+ ` ${this._rgba(C, 0.30)} ${(w - 3).toFixed(1)}deg,`
|
|
97
|
-
+ ` ${this._rgba(C, 0.85)} ${w}deg,`
|
|
98
|
-
+ ` transparent ${w}deg, transparent 360deg)`;
|
|
99
|
-
const sweep = cfg.sweep
|
|
100
|
-
? `<div class="orbit-${outer} o-radar-sweep" style="--o-radar-speed:${cfg.speed}s;background:${beam};"></div>`
|
|
101
|
-
: '';
|
|
102
|
-
|
|
103
|
-
// Blips — each on the orbit matching its distance, aimed by angle.
|
|
104
|
-
const blips = cfg.targets.map((t) => {
|
|
105
|
-
const color = t.color || C;
|
|
106
|
-
const level = Math.min(outer, Math.max(1, Math.round(t.dist * outer)));
|
|
107
|
-
return `<div class="orbit-${level}">
|
|
108
|
-
<div class="satellite" style="--o-angle-composite:calc(${t.angle}deg - 90deg);color:${color};">
|
|
109
|
-
<div class="capsule o-radar-blip" style="width:9px;height:9px;background:${color};box-shadow:0 0 10px ${color};"></div>
|
|
110
|
-
</div>
|
|
111
|
-
</div>`;
|
|
112
|
-
}).join('');
|
|
113
|
-
|
|
114
|
-
const center = `
|
|
115
|
-
<div class="orbit-0">
|
|
116
|
-
<div class="satellite at-center">
|
|
117
|
-
<div class="capsule" style="display:flex;flex-direction:column;align-items:center;gap:4px;">
|
|
118
|
-
<span style="width:6px;height:6px;border-radius:50%;background:${C};box-shadow:0 0 8px ${C};"></span>
|
|
119
|
-
${cfg.label !== null ? `<span style="font-weight:800;font-size:.7em;letter-spacing:.15em;color:${C};">${cfg.label}</span>` : ''}
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
</div>`;
|
|
123
|
-
|
|
124
|
-
this.innerHTML = `
|
|
125
|
-
<div class="bigbang" style="width:${cfg.size};aspect-ratio:1;">
|
|
126
|
-
<div class="gravity-spot">
|
|
127
|
-
${rings}
|
|
128
|
-
${spokes}
|
|
129
|
-
${sweep}
|
|
130
|
-
${blips}
|
|
131
|
-
${center}
|
|
132
|
-
</div>
|
|
133
|
-
</div>`;
|
|
70
|
+
const spokes = cfg.spokes ? `<div class="orbit-0">${'<div class="vector outer-orbit ok-radar-spoke" style="--o-size-ratio:12"></div>'.repeat(cfg.spokes)}</div>` : '';
|
|
71
|
+
const sweep = cfg.sweep ? '<div class="orbit-12 o-radar-sweep"></div>' : '';
|
|
72
|
+
this.innerHTML = universe(cfg.size, `
|
|
73
|
+
<div class="orbit-12 ok-radar-grid"></div>${spokes}${sweep}${cfg.targets.map((t) => this._target(t)).join('')}
|
|
74
|
+
<div class="orbit-0"><div class="satellite at-center"><div class="capsule ok-radar-center"><span class="ok-radar-label">${e(cfg.label ?? '')}</span></div></div></div>`,
|
|
75
|
+
{ spot: `--ok-radar-rings:${cfg.rings};--ok-radar-beam:${cfg.sweepWidth}deg` });
|
|
76
|
+
this._scope = this.querySelector('.gravity-spot');
|
|
77
|
+
this._targets = [...this.querySelectorAll('.ok-radar-target')];
|
|
78
|
+
this._label = this.querySelector('.ok-radar-label');
|
|
79
|
+
this._sweep = this.querySelector('.o-radar-sweep');
|
|
134
80
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
81
|
+
sync(cfg) {
|
|
82
|
+
this._applyTheme({ '--ok-radar-color': cfg.color, '--ok-radar-speed': `${cfg.speed}s` });
|
|
83
|
+
// Reconcile only the target layers, keeping the animated sweep node alive.
|
|
84
|
+
while (this._targets.length > cfg.targets.length) this._targets.pop().remove();
|
|
85
|
+
while (this._targets.length < cfg.targets.length && this._scope?.insertAdjacentHTML) {
|
|
86
|
+
this._scope.insertAdjacentHTML('beforeend', this._target(cfg.targets[this._targets.length]));
|
|
87
|
+
this._targets.push(this._scope.lastElementChild);
|
|
88
|
+
}
|
|
89
|
+
this._targets.forEach((layer, i) => {
|
|
90
|
+
const t = cfg.targets[i];
|
|
91
|
+
const satellite = layer.querySelector('.satellite');
|
|
92
|
+
satellite.style.setProperty('--o-angle-composite', `${fixed(t.angle - 90)}deg`);
|
|
93
|
+
satellite.style.setProperty('--o-alignment', `calc(var(--o-radius) * ${fixed(1 - t.dist, 4)})`);
|
|
94
|
+
if (t.color) satellite.style.setProperty('color', t.color);
|
|
95
|
+
else satellite.style.removeProperty?.('color');
|
|
96
|
+
});
|
|
97
|
+
if (this._label) { this._label.textContent = cfg.label ?? ''; this._label.hidden = !cfg.label; }
|
|
98
|
+
this._describe([cfg.label || 'Radar', ...cfg.targets.map((t, i) => `${t.label ?? `Target ${i + 1}`}: ${t.angle} degrees, ${this._formatOptions() || this._formatter ? `distance ${this._formatText(t.raw, { min: cfg.min, max: cfg.max, kind: 'aria' })}` : `${Math.round(t.dist * 100)}% distance`}`)].join('; '));
|
|
142
99
|
}
|
|
143
100
|
}
|
|
144
|
-
|
|
101
|
+
injectStyleOnce('orbit-kit-radar', RADAR_CSS);
|
|
145
102
|
define('o-radar', OrbitRadar);
|