1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
/**
<slider-input />
A Web Component implementing a slider with a corresponding number input.
TODO rename this file
### Parameters
All of these are optional, see {@linkcode dflt} for defaults.
#### min
Minimum allowed value.
#### max
Maximum allowed value.
#### step
How large each step of the slider/number box should be.
@module
*/
export { SliderInput, Attribute, dflt }
import { makeElement } from '../lib'
/** Defalut values for all attributes, if not given */
const dflt = {
min: 0,
max: 100,
step: 1,
}
/** Valid attributes for SliderInput */
type Attribute = 'min' | 'max' | 'step'
/**
Component displaying an input slider, together with a corresponding numerical
input
*/
class SliderInput extends HTMLElement {
/* value a string since javascript kind of expects that */
#value = "" + dflt.min
/** Minimum allowed value */
min = dflt.min
/** Maximum allowed value */
max = dflt.max
/** How large each step should be */
step = dflt.step
/** The HTML slider component */
readonly slider: HTMLInputElement;
/** The HTML number input component */
readonly textIn: HTMLInputElement;
constructor(min?: number, max?: number, step?: number, value?: number) {
super();
this.min = min || parseFloat(this.getAttribute('min') || "" + dflt['min']);
this.max = max || parseFloat(this.getAttribute('max') || "" + dflt['max']);
this.step = step || parseFloat(this.getAttribute('step') || "" + dflt['step']);
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range#value
const defaultValue
= (this.max < this.min)
? this.min
: this.min + (this.max - this.min) / 2;
this.slider = makeElement('input', {
type: 'range',
min: this.min,
max: this.max,
step: this.step,
value: this.value,
}) as HTMLInputElement
this.textIn = makeElement('input', {
type: 'number',
min: this.min,
max: this.max,
step: this.step,
value: this.value,
}) as HTMLInputElement
this.slider.addEventListener('input', e => this.#propagate(e));
this.textIn.addEventListener('input', e => this.#propagate(e));
/* MUST be after sub components are bound */
this.value = "" + (value || this.getAttribute('value') || defaultValue);
}
connectedCallback() {
this.replaceChildren(this.slider, this.textIn);
}
/** ['min', 'max', 'step'] */
static get observedAttributes(): Attribute[] {
return ['min', 'max', 'step']
}
attributeChangedCallback(name: Attribute, _?: string, to?: string): void {
if (to) {
this.slider.setAttribute(name, to);
this.textIn.setAttribute(name, to);
} else {
this.slider.removeAttribute(name);
this.textIn.removeAttribute(name);
}
this[name] = parseFloat(to || "" + dflt[name])
}
/**
Helper for updating the value attribute
Event listeners are bound on both the input elements, which both simply
call this. This procedure then updates the classes value field.
TODO `oninput`?
*/
#propagate(e: Event) {
this.value = (e.target as HTMLInputElement).value;
if (e instanceof InputEvent && this.oninput) {
this.oninput(e);
}
}
/**
Set a new numerical value.
A number not possible due to the current `min`, `max`, and `step`
properties can be set and will work, the slider will however not
properly show it, but rather the closest value it can display.
*/
set value(value: string) {
this.slider.value = value;
this.textIn.value = value;
this.#value = value;
}
/** Get the current numerical value */
get value(): string {
return this.#value;
}
/* TODO do we want to implement this?
* oninput directly on the component already works
* /
addEventListener(type: string, proc: ((e: Event) => void)) {
}
*/
}
|