@vaadin/slider 25.1.0-alpha2 → 25.1.0-alpha4

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.
@@ -4,13 +4,15 @@
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
7
+ import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
7
8
 
8
9
  /**
9
10
  * @polymerMixin
10
11
  * @mixes DisabledMixin
12
+ * @mixes SlotStylesMixin
11
13
  */
12
14
  export const SliderMixin = (superClass) =>
13
- class SliderMixinClass extends DisabledMixin(superClass) {
15
+ class SliderMixinClass extends SlotStylesMixin(DisabledMixin(superClass)) {
14
16
  static get properties() {
15
17
  return {
16
18
  /**
@@ -56,17 +58,39 @@ export const SliderMixin = (superClass) =>
56
58
  };
57
59
  }
58
60
 
61
+ /** @protected */
62
+ get slotStyles() {
63
+ const tag = this.localName;
64
+
65
+ return [
66
+ `
67
+ ${tag} > input::-webkit-slider-runnable-track {
68
+ height: 100%;
69
+ }
70
+
71
+ ${tag} > input::-webkit-slider-thumb {
72
+ appearance: none;
73
+ width: var(--_thumb-width);
74
+ height: 100%;
75
+ /* iOS needs these */
76
+ background: transparent;
77
+ box-shadow: none;
78
+ }
79
+
80
+ ${tag} > input::-moz-range-thumb {
81
+ border: 0;
82
+ background: transparent;
83
+ width: var(--_thumb-width);
84
+ height: 100%;
85
+ }
86
+ `,
87
+ ];
88
+ }
89
+
59
90
  constructor() {
60
91
  super();
61
92
 
62
- this.__onPointerMove = this.__onPointerMove.bind(this);
63
- this.__onPointerUp = this.__onPointerUp.bind(this);
64
-
65
- // Use separate mousedown listener for focusing the input, as
66
- // pointerdown fires too early and the global `keyboardActive`
67
- // flag isn't updated yet, which incorrectly shows focus-ring
68
93
  this.addEventListener('mousedown', (e) => this.__onMouseDown(e));
69
- this.addEventListener('pointerdown', (e) => this.__onPointerDown(e));
70
94
  }
71
95
 
72
96
  /** @protected */
@@ -76,14 +100,6 @@ export const SliderMixin = (superClass) =>
76
100
  this.__lastCommittedValue = this.value;
77
101
  }
78
102
 
79
- /**
80
- * @param {Event} event
81
- * @return {number}
82
- */
83
- __getThumbIndex(_event) {
84
- return 0;
85
- }
86
-
87
103
  /**
88
104
  * @param {number} value
89
105
  * @param {number} index
@@ -102,7 +118,9 @@ export const SliderMixin = (superClass) =>
102
118
  const nearestOffset = Math.round(offset / step) * step;
103
119
  const nearestValue = minValue + nearestOffset;
104
120
 
105
- const newValue = Math.round(nearestValue);
121
+ // Ensure the last value matching step is used below the max limit.
122
+ // Example: max = 100, step = 1.5 - force maximum allowed value to 99.
123
+ const newValue = nearestValue <= maxValue ? nearestValue : nearestValue - step;
106
124
 
107
125
  this.__value = fullValue.with(index, newValue);
108
126
  }
@@ -126,39 +144,8 @@ export const SliderMixin = (superClass) =>
126
144
  */
127
145
  __getPercentFromValue(value) {
128
146
  const { min, max } = this.__getConstraints();
129
- return (100 * (value - min)) / (max - min);
130
- }
131
-
132
- /**
133
- * @param {number} percent
134
- * @return {number}
135
- * @private
136
- */
137
- __getValueFromPercent(percent) {
138
- const { min, max } = this.__getConstraints();
139
- return min + percent * (max - min);
140
- }
141
-
142
- /**
143
- * @param {PointerEvent} event
144
- * @return {number}
145
- * @private
146
- */
147
- __getEventPercent(event) {
148
- const offset = event.offsetX;
149
- const size = this.offsetWidth;
150
- const safeOffset = Math.min(Math.max(offset, 0), size);
151
- return safeOffset / size;
152
- }
153
-
154
- /**
155
- * @param {PointerEvent} event
156
- * @return {number}
157
- * @private
158
- */
159
- __getEventValue(event) {
160
- const percent = this.__getEventPercent(event);
161
- return this.__getValueFromPercent(percent);
147
+ const safeValue = Math.min(Math.max(value, min), max);
148
+ return (safeValue - min) / (max - min);
162
149
  }
163
150
 
164
151
  /**
@@ -166,80 +153,16 @@ export const SliderMixin = (superClass) =>
166
153
  * @private
167
154
  */
168
155
  __onMouseDown(event) {
169
- const part = event.composedPath()[0].getAttribute('part');
170
- if (!part || (!part.startsWith('track') && !part.startsWith('thumb'))) {
156
+ if (!event.composedPath().includes(this.$.controls)) {
171
157
  return;
172
158
  }
173
159
 
174
- // Prevent losing focus
175
- event.preventDefault();
176
-
177
- this.__focusInput(event);
178
- }
179
-
180
- /**
181
- * @param {PointerEvent} event
182
- * @private
183
- */
184
- __onPointerDown(event) {
185
- if (this.disabled || this.readonly || event.button !== 0) {
186
- return;
187
- }
188
-
189
- // Only handle pointerdown on the thumb, track or track-fill
190
- const part = event.composedPath()[0].getAttribute('part');
191
- if (!part || (!part.startsWith('track') && !part.startsWith('thumb'))) {
192
- return;
193
- }
194
-
195
- this.setPointerCapture(event.pointerId);
196
- this.addEventListener('pointermove', this.__onPointerMove);
197
- this.addEventListener('pointerup', this.__onPointerUp);
198
- this.addEventListener('pointercancel', this.__onPointerUp);
199
-
200
- this.__thumbIndex = this.__getThumbIndex(event);
201
-
202
- // Update value on track click
203
- if (part.startsWith('track')) {
204
- const newValue = this.__getEventValue(event);
205
- this.__updateValue(newValue, this.__thumbIndex);
206
- this.__commitValue();
160
+ // Prevent modifying value when readonly
161
+ if (this.readonly) {
162
+ event.preventDefault();
207
163
  }
208
164
  }
209
165
 
210
- /**
211
- * @param {PointerEvent} event
212
- * @private
213
- */
214
- __onPointerMove(event) {
215
- const newValue = this.__getEventValue(event);
216
- this.__updateValue(newValue, this.__thumbIndex);
217
- this.__commitValue();
218
- }
219
-
220
- /**
221
- * @param {PointerEvent} event
222
- * @private
223
- */
224
- __onPointerUp(event) {
225
- this.__thumbIndex = null;
226
-
227
- this.releasePointerCapture(event.pointerId);
228
- this.removeEventListener('pointermove', this.__onPointerMove);
229
- this.removeEventListener('pointerup', this.__onPointerUp);
230
- this.removeEventListener('pointercancel', this.__onPointerUp);
231
-
232
- this.__detectAndDispatchChange();
233
- }
234
-
235
- /**
236
- * @param {Event} event
237
- * @private
238
- */
239
- __focusInput(_event) {
240
- this.focus({ focusVisible: false });
241
- }
242
-
243
166
  /** @private */
244
167
  __detectAndDispatchChange() {
245
168
  if (JSON.stringify(this.__lastCommittedValue) !== JSON.stringify(this.value)) {
@@ -248,16 +171,6 @@ export const SliderMixin = (superClass) =>
248
171
  }
249
172
  }
250
173
 
251
- /**
252
- * @param {Event} event
253
- * @private
254
- */
255
- __onInput(event) {
256
- const index = this.__getThumbIndex(event);
257
- this.__updateValue(event.target.value, index);
258
- this.__commitValue();
259
- }
260
-
261
174
  /**
262
175
  * @param {Event} event
263
176
  * @private
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
7
7
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
8
+ import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
8
9
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
9
10
  import { SliderMixin } from './vaadin-slider-mixin.js';
10
11
 
@@ -39,7 +40,7 @@ export interface SliderEventMap extends HTMLElementEventMap, SliderCustomEventMa
39
40
  * @fires {Event} change - Fired when the user commits a value change.
40
41
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
41
42
  */
42
- declare class Slider extends SliderMixin(FocusMixin(ThemableMixin(ElementMixin(HTMLElement)))) {
43
+ declare class Slider extends FieldMixin(SliderMixin(FocusMixin(ThemableMixin(ElementMixin(HTMLElement))))) {
43
44
  /**
44
45
  * The value of the slider.
45
46
  */
@@ -10,6 +10,8 @@ import { defineCustomElement } from '@vaadin/component-base/src/define.js';
10
10
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
11
11
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
12
12
  import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
13
+ import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
14
+ import { field } from '@vaadin/field-base/src/styles/field-base-styles.js';
13
15
  import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
14
16
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
15
17
  import { sliderStyles } from './styles/vaadin-slider-base-styles.js';
@@ -29,12 +31,13 @@ import { SliderMixin } from './vaadin-slider-mixin.js';
29
31
  * @customElement
30
32
  * @extends HTMLElement
31
33
  * @mixes ElementMixin
34
+ * @mixes FieldMixin
32
35
  * @mixes FocusMixin
33
36
  * @mixes SliderMixin
34
37
  * @mixes ThemableMixin
35
38
  */
36
- class Slider extends SliderMixin(
37
- FocusMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement))))),
39
+ class Slider extends FieldMixin(
40
+ SliderMixin(FocusMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement)))))),
38
41
  ) {
39
42
  static get is() {
40
43
  return 'vaadin-slider';
@@ -42,15 +45,27 @@ class Slider extends SliderMixin(
42
45
 
43
46
  static get styles() {
44
47
  return [
48
+ field,
45
49
  sliderStyles,
46
50
  css`
47
51
  :host([focus-ring]) [part='thumb'] {
48
- outline: var(--vaadin-focus-ring-width) solid var(--vaadin-focus-ring-color);
52
+ outline: var(--vaadin-focus-ring-width) var(--_outline-style, solid) var(--vaadin-focus-ring-color);
49
53
  outline-offset: 1px;
50
54
  }
51
55
 
52
- :host([readonly][focus-ring]) [part~='thumb'] {
53
- outline-style: dashed;
56
+ #controls {
57
+ grid-template-columns:
58
+ [track-start fill-start]
59
+ calc(var(--value) * var(--_track-width))
60
+ [fill-end thumb1]
61
+ var(--_thumb-width)
62
+ calc((1 - var(--value)) * var(--_track-width))
63
+ [track-end];
64
+ }
65
+
66
+ [part='track-fill'] {
67
+ border-start-start-radius: inherit;
68
+ border-end-start-radius: inherit;
54
69
  }
55
70
  `,
56
71
  ];
@@ -60,6 +75,10 @@ class Slider extends SliderMixin(
60
75
  return 'sliderComponent';
61
76
  }
62
77
 
78
+ static get lumoInjector() {
79
+ return { ...super.lumoInjector, includeBaseStyles: true };
80
+ }
81
+
63
82
  static get properties() {
64
83
  return {
65
84
  /**
@@ -80,17 +99,28 @@ class Slider extends SliderMixin(
80
99
  const percent = this.__getPercentFromValue(value);
81
100
 
82
101
  return html`
83
- <div part="track">
84
- <div
85
- part="track-fill"
86
- style="${styleMap({
87
- insetInlineStart: 0,
88
- insetInlineEnd: `${100 - percent}%`,
89
- })}"
90
- ></div>
102
+ <div class="vaadin-slider-container">
103
+ <div part="label" @click="${this.focus}">
104
+ <slot name="label"></slot>
105
+ <span part="required-indicator" aria-hidden="true"></span>
106
+ </div>
107
+
108
+ <div id="controls" style="${styleMap({ '--value': percent })}">
109
+ <div part="track">
110
+ <div part="track-fill"></div>
111
+ </div>
112
+ <div part="thumb"></div>
113
+ <slot name="input"></slot>
114
+ </div>
115
+
116
+ <div part="helper-text">
117
+ <slot name="helper"></slot>
118
+ </div>
119
+
120
+ <div part="error-message">
121
+ <slot name="error-message"></slot>
122
+ </div>
91
123
  </div>
92
- <div part="thumb" style="${styleMap({ insetInlineStart: `${percent}%` })}"></div>
93
- <slot name="input"></slot>
94
124
  `;
95
125
  }
96
126
 
@@ -107,6 +137,7 @@ class Slider extends SliderMixin(
107
137
 
108
138
  const input = this.querySelector('[slot="input"]');
109
139
  this._inputElement = input;
140
+ this.ariaTarget = input;
110
141
  }
111
142
 
112
143
  /**
@@ -146,7 +177,7 @@ class Slider extends SliderMixin(
146
177
  updated(props) {
147
178
  super.updated(props);
148
179
 
149
- if (props.has('value') || props.has('min') || props.has('max')) {
180
+ if (props.has('value') || props.has('min') || props.has('max') || props.has('step')) {
150
181
  this.__updateValue(this.value);
151
182
  }
152
183
  }
@@ -168,6 +199,16 @@ class Slider extends SliderMixin(
168
199
  super.focus(options);
169
200
  }
170
201
 
202
+ /**
203
+ * @protected
204
+ * @override
205
+ */
206
+ blur() {
207
+ if (this._inputElement) {
208
+ this._inputElement.blur();
209
+ }
210
+ }
211
+
171
212
  /**
172
213
  * @private
173
214
  * @override
@@ -176,6 +217,12 @@ class Slider extends SliderMixin(
176
217
  this.value = this.__value[0];
177
218
  }
178
219
 
220
+ /** @private */
221
+ __onInput(event) {
222
+ this.__updateValue(event.target.value, 0);
223
+ this.__commitValue();
224
+ }
225
+
179
226
  /** @private */
180
227
  __onKeyDown(event) {
181
228
  const arrowKeys = ['ArrowLeft', 'ArrowDown', 'ArrowRight', 'ArrowUp'];