@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-knob.js
CHANGED
|
@@ -1,191 +1,199 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* <o-knob
|
|
2
|
+
* <o-knob>: a rotary slider. Grab the band and turn it, drag anywhere else
|
|
3
|
+
* vertically, use the wheel while focused, or press arrows / Page Up·Down /
|
|
4
|
+
* Home·End. Emits `input` while changing and `change` when a gesture commits,
|
|
5
|
+
* like <input type="range">; setting `value` from code fires neither.
|
|
6
|
+
* It is a form control (FormControl): `name`, `disabled`, form reset/restore.
|
|
3
7
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* .bigbang > .gravity-spot
|
|
7
|
-
* ├── .orbit-N > <o-arc shape="circle-a"> (the dial)
|
|
8
|
-
* └── .orbit-0 > .satellite.at-center > .capsule (value readout)
|
|
9
|
-
*
|
|
10
|
-
* ...into one tag that behaves like a form control:
|
|
11
|
-
*
|
|
12
|
-
* <o-knob value="65" min="0" max="100" unit="%" fill="#a1a1aa"></o-knob>
|
|
13
|
-
*
|
|
14
|
-
* Interaction: drag vertically (or use the mouse wheel / arrow keys when
|
|
15
|
-
* focused) to change the value. Emits a bubbling `input` event on every change
|
|
16
|
-
* and `change` when the gesture ends. `el.value` reads/writes the number.
|
|
8
|
+
* The track is an `interactive` <o-arc> with a `hit-width`, so the whole band
|
|
9
|
+
* takes the pointer even where it is transparent.
|
|
17
10
|
*/
|
|
18
|
-
import { OrbitKitElement, define } from './base.js';
|
|
11
|
+
import { OrbitKitElement, FormControl, define, injectStyleOnce, universe, hub, fixed, escapeHTML, finiteNumber, snapValue, pointerAngle, valueAtAngle, withShared, RANGE_ATTRS, FORMAT_ATTRS, FORM_ATTRS } from './base.js';
|
|
19
12
|
|
|
20
|
-
|
|
13
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
14
|
+
|
|
15
|
+
export class OrbitKnob extends FormControl(OrbitKitElement) {
|
|
21
16
|
static get observedAttributes() {
|
|
22
|
-
return ['value', '
|
|
23
|
-
|
|
17
|
+
return withShared(['value', 'size', 'orbit', 'thickness', 'fill', 'track',
|
|
18
|
+
'stroke-width', 'shape', 'label', 'range', 'from', 'glow'], RANGE_ATTRS, FORMAT_ATTRS, FORM_ATTRS);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
for (const name of ['_onDown', '_onMove', '_onUp', '_onWheel', '_onKey']) {
|
|
24
|
+
this[name] = this[name].bind(this);
|
|
25
|
+
}
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
readConfig() {
|
|
27
|
-
const range = this._num('range', 270);
|
|
28
|
-
const
|
|
29
|
-
const max = this._num('max', 100);
|
|
29
|
+
const range = clamp(this._num('range', 270), 0, 360);
|
|
30
|
+
const r = this._range({ min: 0, max: 100, step: 1 });
|
|
30
31
|
return {
|
|
31
|
-
value:
|
|
32
|
-
min, max,
|
|
33
|
-
span: (max - min) || 1,
|
|
34
|
-
step: this._num('step', 1),
|
|
32
|
+
min: r.min, max: r.max, span: r.span || 1, step: r.step, value: r.value, fraction: r.fraction,
|
|
35
33
|
range,
|
|
36
34
|
from: this.hasAttribute('from') ? this._num('from', 0) : this._autoFrom(range),
|
|
37
|
-
|
|
38
|
-
size: this.
|
|
39
|
-
fill: this.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
unit: this._str('unit', ''),
|
|
44
|
-
disabled: this.hasAttribute('disabled'),
|
|
45
|
-
glow: this.getAttribute('glow'),
|
|
35
|
+
thickness: this._thickness(2.4),
|
|
36
|
+
size: this._size('size', '160px'),
|
|
37
|
+
fill: this._color('fill', null), track: this._color('track', null),
|
|
38
|
+
strokeWidth: Math.max(0, this._num('stroke-width', 0)),
|
|
39
|
+
shape: this._str('shape', 'rounded'), unit: this._unitText(),
|
|
40
|
+
disabled: this._isDisabled(), glow: this.getAttribute('glow'),
|
|
46
41
|
label: this.hasAttribute('label') ? this.getAttribute('label') : null
|
|
47
42
|
};
|
|
48
43
|
}
|
|
49
44
|
|
|
45
|
+
/** Clamp and snap like the attribute (kept for 1.0 callers). */
|
|
46
|
+
_normalize(raw, cfg) { return snapValue(finiteNumber(raw, cfg.min), cfg); }
|
|
47
|
+
|
|
50
48
|
structuralSignature(cfg) {
|
|
51
|
-
const { value, label, disabled, ...rest } = cfg;
|
|
49
|
+
const { value, fraction, min, max, span, step, label, disabled, fill, track, unit, ...rest } = cfg;
|
|
52
50
|
return JSON.stringify(rest);
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
build(cfg) {
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<div class="gravity-spot" style="--o-force:${force}px;">
|
|
66
|
-
<div class="orbit-${cfg.orbit}" style="${ring}"><o-arc value="100" shape="${cfg.shape}" style="${trackStyle}"></o-arc></div>
|
|
67
|
-
<div class="orbit-${cfg.orbit}" style="${ring}"><o-arc class="o-knob-dial" value="0" shape="${cfg.shape}" style="${valStyle}"></o-arc></div>
|
|
68
|
-
<div class="orbit-0">
|
|
69
|
-
<div class="satellite at-center">
|
|
70
|
-
<div class="capsule ok-hub o-knob-readout"></div>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
</div>`;
|
|
75
|
-
|
|
54
|
+
const e = escapeHTML;
|
|
55
|
+
const ring = `--o-range:${cfg.range}deg;--o-from:${cfg.from}deg;--o-size-ratio:${fixed(cfg.thickness)};--o-gap:0`;
|
|
56
|
+
const stroke = cfg.strokeWidth ? `--o-stroke-width:${cfg.strokeWidth};` : '';
|
|
57
|
+
const glow = cfg.glow == null ? '' : `filter:drop-shadow(0 0 ${Math.max(0, this._num('glow', 6))}px var(--ok-fill, #a1a1aa));`;
|
|
58
|
+
this.innerHTML = universe(cfg.size, `
|
|
59
|
+
<div class="orbit-12" style="${ring}"><o-arc aria-hidden="true" class="inner-orbit ok-track o-knob-track" interactive hit-width="6" value="100" shape="${e(cfg.shape)}"${stroke ? ` style="${stroke}"` : ''}></o-arc></div>
|
|
60
|
+
<div class="orbit-12" style="${ring}"><o-arc aria-hidden="true" class="inner-orbit ok-value o-knob-dial" value="0" shape="${e(cfg.shape)}" ${stroke || glow ? ` style="${stroke}${glow}"` : ''}></o-arc></div>
|
|
61
|
+
${hub(2 * (12 - cfg.thickness) - 1, '', 'ok-hub o-knob-readout', ' aria-hidden="true"')}`);
|
|
62
|
+
this._box = this.querySelector('.bigbang');
|
|
76
63
|
this._dial = this.querySelector('.o-knob-dial');
|
|
77
64
|
this._readout = this.querySelector('.o-knob-readout');
|
|
78
|
-
|
|
79
|
-
// Accessibility: act as a slider.
|
|
80
65
|
this.setAttribute('role', 'slider');
|
|
81
|
-
this.tabIndex = cfg.disabled ? -1 : 0;
|
|
82
|
-
this.style.touchAction = 'none';
|
|
83
|
-
this.style.cursor = cfg.disabled ? 'default' : 'ns-resize';
|
|
84
66
|
}
|
|
85
67
|
|
|
86
68
|
sync(cfg) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (this._readout)
|
|
91
|
-
|
|
69
|
+
this._applyTheme({ '--ok-fill': cfg.fill, '--ok-track': cfg.track });
|
|
70
|
+
if (this._dial) this._dial.setAttribute('value', (cfg.fraction * 100).toFixed(2));
|
|
71
|
+
const text = this._formatText(cfg.value, { min: cfg.min, max: cfg.max });
|
|
72
|
+
if (this._readout) this._readout.textContent = cfg.label ?? text;
|
|
73
|
+
this._ariaRange(cfg, cfg.value, this._formatText(cfg.value, { min: cfg.min, max: cfg.max, kind: 'aria' }));
|
|
74
|
+
if (!this.hasAttribute('aria-labelledby') && (!this.hasAttribute('aria-label') || this.getAttribute('aria-label') === this._autoLabel)) {
|
|
75
|
+
this._autoLabel = cfg.label || 'Rotary control';
|
|
76
|
+
this.setAttribute('aria-label', this._autoLabel);
|
|
92
77
|
}
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
78
|
+
this.tabIndex = cfg.disabled ? -1 : 0;
|
|
79
|
+
this.style.touchAction = cfg.disabled ? 'auto' : 'none';
|
|
80
|
+
this.style.cursor = cfg.disabled ? 'default' : 'ns-resize';
|
|
81
|
+
this.toggleAttribute?.('data-disabled', cfg.disabled);
|
|
82
|
+
if (this._box) this._box.style.opacity = cfg.disabled ? '.5' : '';
|
|
83
|
+
if (cfg.disabled) this._stopDrag();
|
|
96
84
|
}
|
|
97
85
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
set value(
|
|
86
|
+
get value() { return this.readConfig().value; }
|
|
87
|
+
/** Setting the value snaps and clamps it and fires no event, like a native input. */
|
|
88
|
+
set value(value) { this._commit(finiteNumber(value, this.value), { silent: true }); }
|
|
89
|
+
get valueAsNumber() { return this.value; }
|
|
90
|
+
get min() { return this.readConfig().min; }
|
|
91
|
+
set min(value) { this.setAttribute('min', String(value)); }
|
|
92
|
+
get max() { return this.readConfig().max; }
|
|
93
|
+
set max(value) { this.setAttribute('max', String(value)); }
|
|
94
|
+
get step() { return this.readConfig().step; }
|
|
95
|
+
set step(value) { this.setAttribute('step', String(value)); }
|
|
96
|
+
stepUp(n = 1) { const cfg = this.readConfig(); this._commit(cfg.value + n * (cfg.step || cfg.span / 100), { silent: true }); }
|
|
97
|
+
stepDown(n = 1) { this.stepUp(-n); }
|
|
101
98
|
|
|
102
99
|
onConnected() {
|
|
103
|
-
this._onDown = this._onDown.bind(this);
|
|
104
|
-
this._onMove = this._onMove.bind(this);
|
|
105
|
-
this._onUp = this._onUp.bind(this);
|
|
106
|
-
this._onWheel = this._onWheel.bind(this);
|
|
107
|
-
this._onKey = this._onKey.bind(this);
|
|
108
100
|
this.addEventListener('pointerdown', this._onDown);
|
|
101
|
+
this.addEventListener('lostpointercapture', this._onUp);
|
|
109
102
|
this.addEventListener('wheel', this._onWheel, { passive: false });
|
|
110
103
|
this.addEventListener('keydown', this._onKey);
|
|
111
104
|
}
|
|
112
105
|
|
|
113
106
|
onDisconnected() {
|
|
114
107
|
this.removeEventListener('pointerdown', this._onDown);
|
|
108
|
+
this.removeEventListener('lostpointercapture', this._onUp);
|
|
115
109
|
this.removeEventListener('wheel', this._onWheel);
|
|
116
110
|
this.removeEventListener('keydown', this._onKey);
|
|
117
|
-
|
|
118
|
-
window.removeEventListener('pointerup', this._onUp);
|
|
111
|
+
this._stopDrag();
|
|
119
112
|
}
|
|
120
113
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
this.
|
|
132
|
-
this.
|
|
133
|
-
}
|
|
134
|
-
if (emitChange) {
|
|
135
|
-
this.dispatchEvent(new CustomEvent('change', { detail: { value: v }, bubbles: true }));
|
|
114
|
+
_emit(type, value) { this._emitValue(type, value); }
|
|
115
|
+
|
|
116
|
+
/** Set a value; user gestures fire `input` (and `change` when they commit). */
|
|
117
|
+
_commit(raw, { change = false, silent = false } = {}) {
|
|
118
|
+
const cfg = this.readConfig();
|
|
119
|
+
const value = this._normalize(raw, cfg);
|
|
120
|
+
const changed = value !== cfg.value;
|
|
121
|
+
// The attribute shows the current value; the default (for form reset) stays.
|
|
122
|
+
this._reflectValue(String(value));
|
|
123
|
+
if (changed && !silent) {
|
|
124
|
+
this._emit('input', value);
|
|
125
|
+
if (change) this._emit('change', value);
|
|
136
126
|
}
|
|
137
127
|
}
|
|
138
128
|
|
|
139
|
-
_onDown(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
e.preventDefault();
|
|
129
|
+
_onDown(event) {
|
|
130
|
+
if (this._isDisabled() || event.button !== 0 || this._pointerId != null) return;
|
|
131
|
+
event.preventDefault();
|
|
143
132
|
this.focus();
|
|
144
|
-
this.
|
|
145
|
-
this.
|
|
133
|
+
this._pointerId = event.pointerId;
|
|
134
|
+
this._dragStartY = event.clientY;
|
|
135
|
+
this._dragStartVal = this.value;
|
|
136
|
+
// On the band the knob follows the pointer around; elsewhere it is a vertical drag.
|
|
137
|
+
this._rotary = !!event.target?.closest?.('.o-knob-track');
|
|
138
|
+
if (this._rotary) this._commit(this._valueAt(event));
|
|
139
|
+
this.setPointerCapture?.(event.pointerId);
|
|
146
140
|
window.addEventListener('pointermove', this._onMove);
|
|
147
141
|
window.addEventListener('pointerup', this._onUp);
|
|
142
|
+
window.addEventListener('pointercancel', this._onUp);
|
|
148
143
|
}
|
|
149
144
|
|
|
150
|
-
_onMove(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const delta = (dy / 200) * cfg.span;
|
|
155
|
-
this._commit(this._dragStartVal + delta);
|
|
145
|
+
_onMove(event) {
|
|
146
|
+
if (event.pointerId !== this._pointerId || this._isDisabled()) return;
|
|
147
|
+
if (this._rotary) this._commit(this._valueAt(event));
|
|
148
|
+
else this._commit(this._dragStartVal + (this._dragStartY - event.clientY) / 200 * this.readConfig().span);
|
|
156
149
|
}
|
|
157
150
|
|
|
158
|
-
|
|
151
|
+
/** The value under the pointer, from its angle around the center. */
|
|
152
|
+
_valueAt(event) {
|
|
153
|
+
const cfg = this.readConfig();
|
|
154
|
+
if (!this._box?.getBoundingClientRect || !cfg.range) return cfg.value;
|
|
155
|
+
return valueAtAngle(pointerAngle(event, this._box), cfg);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_stopDrag() {
|
|
159
|
+
if (typeof window === 'undefined') return;
|
|
160
|
+
if (this._pointerId != null && this.hasPointerCapture?.(this._pointerId)) this.releasePointerCapture(this._pointerId);
|
|
161
|
+
this._pointerId = null;
|
|
159
162
|
window.removeEventListener('pointermove', this._onMove);
|
|
160
163
|
window.removeEventListener('pointerup', this._onUp);
|
|
161
|
-
|
|
164
|
+
window.removeEventListener('pointercancel', this._onUp);
|
|
162
165
|
}
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this._commit(this._num('value', cfg.min) + dir * (cfg.step || 1), true);
|
|
167
|
+
_onUp(event) {
|
|
168
|
+
if (event.pointerId !== this._pointerId) return;
|
|
169
|
+
const changed = this.value !== this._dragStartVal;
|
|
170
|
+
this._stopDrag();
|
|
171
|
+
if (changed && !this._isDisabled()) this._emit('change', this.value);
|
|
170
172
|
}
|
|
171
173
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
e.preventDefault(); this._commit(cfg.max, true);
|
|
187
|
-
}
|
|
174
|
+
_onWheel(event) {
|
|
175
|
+
if (this._isDisabled() || this.ownerDocument?.activeElement !== this || !event.deltaY) return;
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
const cfg = this.readConfig();
|
|
178
|
+
this._commit(cfg.value + (event.deltaY < 0 ? 1 : -1) * (cfg.step || cfg.span / 100), { change: true });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
_onKey(event) {
|
|
182
|
+
if (this._isDisabled()) return;
|
|
183
|
+
const cfg = this.readConfig();
|
|
184
|
+
const next = this._keyValue(event, cfg);
|
|
185
|
+
if (next == null) return;
|
|
186
|
+
event.preventDefault();
|
|
187
|
+
this._commit(next, { change: true });
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
injectStyleOnce('orbit-kit-knob', `
|
|
192
|
+
o-knob { -webkit-tap-highlight-color: transparent; }
|
|
193
|
+
o-knob .ok-track { --o-fill: var(--ok-track, #27272a); --o-stroke: var(--o-fill); }
|
|
194
|
+
o-knob .ok-value { --o-fill: var(--ok-fill, #a1a1aa); --o-stroke: var(--o-fill); }
|
|
195
|
+
o-knob .o-knob-track { cursor: grab; }
|
|
196
|
+
o-knob[data-disabled] .o-knob-track { cursor: default; }
|
|
197
|
+
o-knob[data-disabled] > .bigbang { opacity: .5; }
|
|
198
|
+
`);
|
|
191
199
|
define('o-knob', OrbitKnob);
|