1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | import settings from '../../globals/js/settings';
|
9 | import mixin from '../../globals/js/misc/mixin';
|
10 | import createComponent from '../../globals/js/mixins/create-component';
|
11 | import initComponentBySearch from '../../globals/js/mixins/init-component-by-search';
|
12 | import eventedState from '../../globals/js/mixins/evented-state';
|
13 | import handles from '../../globals/js/mixins/handles';
|
14 | import on from '../../globals/js/misc/on';
|
15 |
|
16 | class Slider extends mixin(createComponent, initComponentBySearch, eventedState, handles) {
|
17 | |
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | constructor(element, options) {
|
25 | super(element, options);
|
26 |
|
27 | this.sliderActive = false;
|
28 | this.dragging = false;
|
29 |
|
30 | this.track = this.element.querySelector(this.options.selectorTrack);
|
31 | this.filledTrack = this.element.querySelector(this.options.selectorFilledTrack);
|
32 | this.thumb = this.element.querySelector(this.options.selectorThumb);
|
33 | this.input = this.element.querySelector(this.options.selectorInput);
|
34 |
|
35 | if (this.element.dataset.sliderInputBox) {
|
36 | this.boundInput = this.element.ownerDocument.querySelector(this.element.dataset.sliderInputBox);
|
37 | this._updateInput();
|
38 | this.manage(
|
39 | on(this.boundInput, 'change', evt => {
|
40 | this.setValue(evt.target.value);
|
41 | })
|
42 | );
|
43 | this.manage(
|
44 | on(this.boundInput, 'focus', evt => {
|
45 | evt.target.select();
|
46 | })
|
47 | );
|
48 |
|
49 | this.manage(
|
50 | on(this.boundInput, 'mouseup', evt => {
|
51 | evt.preventDefault();
|
52 | })
|
53 | );
|
54 | }
|
55 |
|
56 | this._updatePosition();
|
57 |
|
58 | this.manage(
|
59 | on(this.thumb, 'mousedown', () => {
|
60 | this.sliderActive = true;
|
61 | })
|
62 | );
|
63 | this.manage(
|
64 | on(this.element.ownerDocument, 'mouseup', () => {
|
65 | this.sliderActive = false;
|
66 | })
|
67 | );
|
68 | this.manage(
|
69 | on(this.element.ownerDocument, 'mousemove', evt => {
|
70 | const disabled = this.element.classList.contains(this.options.classDisabled);
|
71 | if (this.sliderActive === true && !disabled) {
|
72 | this._updatePosition(evt);
|
73 | }
|
74 | })
|
75 | );
|
76 | this.manage(
|
77 | on(this.thumb, 'keydown', evt => {
|
78 | const disabled = this.element.classList.contains(this.options.classDisabled);
|
79 | if (!disabled) {
|
80 | this._updatePosition(evt);
|
81 | }
|
82 | })
|
83 | );
|
84 | this.manage(
|
85 | on(this.track, 'click', evt => {
|
86 | const disabled = this.element.classList.contains(this.options.classDisabled);
|
87 | if (!disabled) {
|
88 | this._updatePosition(evt);
|
89 | }
|
90 | })
|
91 | );
|
92 | }
|
93 |
|
94 | _changeState = (state, detail, callback) => {
|
95 | callback();
|
96 | };
|
97 |
|
98 | _updatePosition(evt) {
|
99 | const { left, newValue } = this._calcValue(evt);
|
100 |
|
101 | if (this.dragging) {
|
102 | return;
|
103 | }
|
104 |
|
105 | this.dragging = true;
|
106 |
|
107 | requestAnimationFrame(() => {
|
108 | this.dragging = false;
|
109 | this.thumb.style.left = `${left}%`;
|
110 | this.filledTrack.style.transform = `translate(0%, -50%) scaleX(${left / 100})`;
|
111 | this.input.value = newValue;
|
112 | this._updateInput();
|
113 | this.changeState('slider-value-change', { value: newValue });
|
114 | });
|
115 | }
|
116 |
|
117 | _calcValue(evt) {
|
118 | const { value, min, max, step } = this.getInputProps();
|
119 |
|
120 | const range = max - min;
|
121 | const valuePercentage = ((value - min) / range) * 100;
|
122 |
|
123 | let left;
|
124 | let newValue;
|
125 | left = valuePercentage;
|
126 | newValue = value;
|
127 |
|
128 | if (evt) {
|
129 | const { type } = evt;
|
130 |
|
131 | if (type === 'keydown') {
|
132 | const direction = {
|
133 | 40: -1,
|
134 | 37: -1,
|
135 | 38: 1,
|
136 | 39: 1,
|
137 | }[evt.which];
|
138 |
|
139 | if (direction !== undefined) {
|
140 | const multiplier = evt.shiftKey === true ? range / step / this.options.stepMultiplier : 1;
|
141 | const stepMultiplied = step * multiplier;
|
142 | const stepSize = (stepMultiplied / range) * 100;
|
143 | left = valuePercentage + stepSize * direction;
|
144 | newValue = Number(value) + stepMultiplied * direction;
|
145 | }
|
146 | }
|
147 | if (type === 'mousemove' || type === 'click') {
|
148 | if (type === 'click') {
|
149 | this.element.querySelector(this.options.selectorThumb).classList.add(this.options.classThumbClicked);
|
150 | } else {
|
151 | this.element.querySelector(this.options.selectorThumb).classList.remove(this.options.classThumbClicked);
|
152 | }
|
153 |
|
154 | const track = this.track.getBoundingClientRect();
|
155 | const unrounded = (evt.clientX - track.left) / track.width;
|
156 | const rounded = Math.round((range * unrounded) / step) * step;
|
157 | left = (rounded / range) * 100;
|
158 | newValue = rounded + min;
|
159 | }
|
160 | }
|
161 |
|
162 | if (newValue <= Number(min)) {
|
163 | left = 0;
|
164 | newValue = min;
|
165 | }
|
166 | if (newValue >= Number(max)) {
|
167 | left = 100;
|
168 | newValue = max;
|
169 | }
|
170 |
|
171 | return { left, newValue };
|
172 | }
|
173 |
|
174 | _updateInput() {
|
175 | if (this.boundInput) {
|
176 | this.boundInput.value = this.input.value;
|
177 | }
|
178 | }
|
179 |
|
180 | getInputProps() {
|
181 | const values = {
|
182 | value: Number(this.input.value),
|
183 | min: Number(this.input.min),
|
184 | max: Number(this.input.max),
|
185 | step: this.input.step ? Number(this.input.step) : 1,
|
186 | };
|
187 | return values;
|
188 | }
|
189 |
|
190 | setValue(value) {
|
191 | this.input.value = value;
|
192 | this._updatePosition();
|
193 | }
|
194 |
|
195 | stepUp() {
|
196 | this.input.stepUp();
|
197 | this._updatePosition();
|
198 | }
|
199 |
|
200 | stepDown() {
|
201 | this.input.stepDown();
|
202 | this._updatePosition();
|
203 | }
|
204 |
|
205 | |
206 |
|
207 |
|
208 |
|
209 | static components = new WeakMap();
|
210 |
|
211 | |
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 | static get options() {
|
218 | const { prefix } = settings;
|
219 | return {
|
220 | selectorInit: '[data-slider]',
|
221 | selectorTrack: `.${prefix}--slider__track`,
|
222 | selectorFilledTrack: `.${prefix}--slider__filled-track`,
|
223 | selectorThumb: `.${prefix}--slider__thumb`,
|
224 | selectorInput: `.${prefix}--slider__input`,
|
225 | classDisabled: `${prefix}--slider--disabled`,
|
226 | classThumbClicked: `${prefix}--slider__thumb--clicked`,
|
227 | eventBeforeSliderValueChange: 'slider-before-value-change',
|
228 | eventAfterSliderValueChange: 'slider-after-value-change',
|
229 | stepMultiplier: 4,
|
230 | };
|
231 | }
|
232 | }
|
233 |
|
234 | export default Slider;
|