@vaadin/slider 25.1.0-alpha3 → 25.1.0-alpha5

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
@@ -128,39 +144,8 @@ export const SliderMixin = (superClass) =>
128
144
  */
129
145
  __getPercentFromValue(value) {
130
146
  const { min, max } = this.__getConstraints();
131
- return (100 * (value - min)) / (max - min);
132
- }
133
-
134
- /**
135
- * @param {number} percent
136
- * @return {number}
137
- * @private
138
- */
139
- __getValueFromPercent(percent) {
140
- const { min, max } = this.__getConstraints();
141
- return min + percent * (max - min);
142
- }
143
-
144
- /**
145
- * @param {PointerEvent} event
146
- * @return {number}
147
- * @private
148
- */
149
- __getEventPercent(event) {
150
- const offset = event.offsetX;
151
- const size = this.offsetWidth;
152
- const safeOffset = Math.min(Math.max(offset, 0), size);
153
- return safeOffset / size;
154
- }
155
-
156
- /**
157
- * @param {PointerEvent} event
158
- * @return {number}
159
- * @private
160
- */
161
- __getEventValue(event) {
162
- const percent = this.__getEventPercent(event);
163
- return this.__getValueFromPercent(percent);
147
+ const safeValue = Math.min(Math.max(value, min), max);
148
+ return (safeValue - min) / (max - min);
164
149
  }
165
150
 
166
151
  /**
@@ -168,78 +153,19 @@ export const SliderMixin = (superClass) =>
168
153
  * @private
169
154
  */
170
155
  __onMouseDown(event) {
171
- const part = event.composedPath()[0].getAttribute('part');
172
- if (!part || (!part.startsWith('track') && !part.startsWith('thumb'))) {
173
- return;
174
- }
175
-
176
- // Prevent losing focus
177
- event.preventDefault();
178
-
179
- this.__focusInput(event);
180
- }
181
-
182
- /**
183
- * @param {PointerEvent} event
184
- * @private
185
- */
186
- __onPointerDown(event) {
187
- if (this.disabled || this.readonly || event.button !== 0) {
188
- return;
189
- }
190
-
191
- // Only handle pointerdown on the thumb, track or track-fill
192
- const part = event.composedPath()[0].getAttribute('part');
193
- if (!part || (!part.startsWith('track') && !part.startsWith('thumb'))) {
156
+ if (!event.composedPath().includes(this.$.controls)) {
194
157
  return;
195
158
  }
196
159
 
197
- this.setPointerCapture(event.pointerId);
198
- this.addEventListener('pointermove', this.__onPointerMove);
199
- this.addEventListener('pointerup', this.__onPointerUp);
200
- this.addEventListener('pointercancel', this.__onPointerUp);
201
-
202
- this.__thumbIndex = this.__getThumbIndex(event);
203
-
204
- // Update value on track click
205
- if (part.startsWith('track')) {
206
- const newValue = this.__getEventValue(event);
207
- this.__updateValue(newValue, this.__thumbIndex);
208
- this.__commitValue();
160
+ // Prevent modifying value when readonly
161
+ if (this.readonly) {
162
+ event.preventDefault();
209
163
  }
210
164
  }
211
165
 
212
- /**
213
- * @param {PointerEvent} event
214
- * @private
215
- */
216
- __onPointerMove(event) {
217
- const newValue = this.__getEventValue(event);
218
- this.__updateValue(newValue, this.__thumbIndex);
219
- this.__commitValue();
220
- }
221
-
222
- /**
223
- * @param {PointerEvent} event
224
- * @private
225
- */
226
- __onPointerUp(event) {
227
- this.__thumbIndex = null;
228
-
229
- this.releasePointerCapture(event.pointerId);
230
- this.removeEventListener('pointermove', this.__onPointerMove);
231
- this.removeEventListener('pointerup', this.__onPointerUp);
232
- this.removeEventListener('pointercancel', this.__onPointerUp);
233
-
234
- this.__detectAndDispatchChange();
235
- }
236
-
237
- /**
238
- * @param {Event} event
239
- * @private
240
- */
241
- __focusInput(_event) {
242
- this.focus({ focusVisible: false });
166
+ /** @private */
167
+ __dispatchInputEvent() {
168
+ this.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
243
169
  }
244
170
 
245
171
  /** @private */
@@ -250,16 +176,6 @@ export const SliderMixin = (superClass) =>
250
176
  }
251
177
  }
252
178
 
253
- /**
254
- * @param {Event} event
255
- * @private
256
- */
257
- __onInput(event) {
258
- const index = this.__getThumbIndex(event);
259
- this.__updateValue(event.target.value, index);
260
- this.__commitValue();
261
- }
262
-
263
179
  /**
264
180
  * @param {Event} event
265
181
  * @private
@@ -269,6 +185,12 @@ export const SliderMixin = (superClass) =>
269
185
  this.__detectAndDispatchChange();
270
186
  }
271
187
 
188
+ /**
189
+ * Fired when the slider value changes during user interaction.
190
+ *
191
+ * @event input
192
+ */
193
+
272
194
  /**
273
195
  * Fired when the user commits a value change.
274
196
  *
@@ -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
 
@@ -15,17 +16,39 @@ export type SliderChangeEvent = Event & {
15
16
  target: Slider;
16
17
  };
17
18
 
19
+ /**
20
+ * Fired when the slider value changes during user interaction.
21
+ */
22
+ export type SliderInputEvent = Event & {
23
+ target: Slider;
24
+ };
25
+
26
+ /**
27
+ * Fired when the `invalid` property changes.
28
+ */
29
+ export type SliderInvalidChangedEvent = CustomEvent<{ value: boolean }>;
30
+
18
31
  /**
19
32
  * Fired when the `value` property changes.
20
33
  */
21
34
  export type SliderValueChangedEvent = CustomEvent<{ value: number }>;
22
35
 
36
+ /**
37
+ * Fired whenever the slider is validated.
38
+ */
39
+ export type SliderValidatedEvent = CustomEvent<{ valid: boolean }>;
40
+
23
41
  export interface SliderCustomEventMap {
42
+ 'invalid-changed': SliderInvalidChangedEvent;
43
+
24
44
  'value-changed': SliderValueChangedEvent;
45
+
46
+ validated: SliderValidatedEvent;
25
47
  }
26
48
 
27
49
  export interface SliderEventMap extends HTMLElementEventMap, SliderCustomEventMap {
28
50
  change: SliderChangeEvent;
51
+ input: SliderInputEvent;
29
52
  }
30
53
 
31
54
  /**
@@ -36,10 +59,58 @@ export interface SliderEventMap extends HTMLElementEventMap, SliderCustomEventMa
36
59
  * <vaadin-slider min="0" max="100" step="1"></vaadin-slider>
37
60
  * ```
38
61
  *
62
+ * ### Styling
63
+ *
64
+ * The following shadow DOM parts are available for styling:
65
+ *
66
+ * Part name | Description
67
+ * ---------------------|-----------------
68
+ * `label` | The label element
69
+ * `required-indicator` | The required indicator element
70
+ * `helper-text` | The helper text element
71
+ * `error-message` | The error message element
72
+ * `track` | The slider track
73
+ * `track-fill` | The filled portion of the track
74
+ * `thumb` | The slider thumb
75
+ *
76
+ * The following state attributes are available for styling:
77
+ *
78
+ * Attribute | Description
79
+ * -------------|-------------
80
+ * `disabled` | Set when the slider is disabled
81
+ * `readonly` | Set when the slider is read-only
82
+ * `focused` | Set when the slider has focus
83
+ * `focus-ring` | Set when the slider is focused using the keyboard
84
+ *
85
+ * The following custom CSS properties are available for styling:
86
+ *
87
+ * Custom CSS property |
88
+ * :--------------------------------------------|
89
+ * `--vaadin-field-default-width` |
90
+ * `--vaadin-input-field-error-color` |
91
+ * `--vaadin-input-field-error-font-size` |
92
+ * `--vaadin-input-field-error-font-weight` |
93
+ * `--vaadin-input-field-helper-color` |
94
+ * `--vaadin-input-field-helper-font-size` |
95
+ * `--vaadin-input-field-helper-font-weight` |
96
+ * `--vaadin-input-field-label-color` |
97
+ * `--vaadin-input-field-label-font-size` |
98
+ * `--vaadin-input-field-label-font-weight` |
99
+ * `--vaadin-input-field-required-indicator` |
100
+ * `--vaadin-slider-fill-background` |
101
+ * `--vaadin-slider-thumb-height` |
102
+ * `--vaadin-slider-thumb-width` |
103
+ * `--vaadin-slider-track-background` |
104
+ * `--vaadin-slider-track-border-radius` |
105
+ * `--vaadin-slider-track-height` |
106
+ *
107
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
108
+ *
39
109
  * @fires {Event} change - Fired when the user commits a value change.
110
+ * @fires {Event} input - Fired when the slider value changes during user interaction.
40
111
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
41
112
  */
42
- declare class Slider extends SliderMixin(FocusMixin(ThemableMixin(ElementMixin(HTMLElement)))) {
113
+ declare class Slider extends FieldMixin(SliderMixin(FocusMixin(ThemableMixin(ElementMixin(HTMLElement))))) {
43
114
  /**
44
115
  * The value of the slider.
45
116
  */
@@ -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';
@@ -23,18 +25,67 @@ import { SliderMixin } from './vaadin-slider-mixin.js';
23
25
  * <vaadin-slider min="0" max="100" step="1"></vaadin-slider>
24
26
  * ```
25
27
  *
28
+ * ### Styling
29
+ *
30
+ * The following shadow DOM parts are available for styling:
31
+ *
32
+ * Part name | Description
33
+ * ---------------------|-----------------
34
+ * `label` | The label element
35
+ * `required-indicator` | The required indicator element
36
+ * `helper-text` | The helper text element
37
+ * `error-message` | The error message element
38
+ * `track` | The slider track
39
+ * `track-fill` | The filled portion of the track
40
+ * `thumb` | The slider thumb
41
+ *
42
+ * The following state attributes are available for styling:
43
+ *
44
+ * Attribute | Description
45
+ * -------------|-------------
46
+ * `disabled` | Set when the slider is disabled
47
+ * `readonly` | Set when the slider is read-only
48
+ * `focused` | Set when the slider has focus
49
+ * `focus-ring` | Set when the slider is focused using the keyboard
50
+ *
51
+ * The following custom CSS properties are available for styling:
52
+ *
53
+ * Custom CSS property |
54
+ * :--------------------------------------------|
55
+ * `--vaadin-field-default-width` |
56
+ * `--vaadin-input-field-error-color` |
57
+ * `--vaadin-input-field-error-font-size` |
58
+ * `--vaadin-input-field-error-font-weight` |
59
+ * `--vaadin-input-field-helper-color` |
60
+ * `--vaadin-input-field-helper-font-size` |
61
+ * `--vaadin-input-field-helper-font-weight` |
62
+ * `--vaadin-input-field-label-color` |
63
+ * `--vaadin-input-field-label-font-size` |
64
+ * `--vaadin-input-field-label-font-weight` |
65
+ * `--vaadin-input-field-required-indicator` |
66
+ * `--vaadin-slider-fill-background` |
67
+ * `--vaadin-slider-thumb-height` |
68
+ * `--vaadin-slider-thumb-width` |
69
+ * `--vaadin-slider-track-background` |
70
+ * `--vaadin-slider-track-border-radius` |
71
+ * `--vaadin-slider-track-height` |
72
+ *
73
+ * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
74
+ *
26
75
  * @fires {Event} change - Fired when the user commits a value change.
76
+ * @fires {Event} input - Fired when the slider value changes during user interaction.
27
77
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
28
78
  *
29
79
  * @customElement
30
80
  * @extends HTMLElement
31
81
  * @mixes ElementMixin
82
+ * @mixes FieldMixin
32
83
  * @mixes FocusMixin
33
84
  * @mixes SliderMixin
34
85
  * @mixes ThemableMixin
35
86
  */
36
- class Slider extends SliderMixin(
37
- FocusMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement))))),
87
+ class Slider extends FieldMixin(
88
+ SliderMixin(FocusMixin(ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement)))))),
38
89
  ) {
39
90
  static get is() {
40
91
  return 'vaadin-slider';
@@ -42,15 +93,27 @@ class Slider extends SliderMixin(
42
93
 
43
94
  static get styles() {
44
95
  return [
96
+ field,
45
97
  sliderStyles,
46
98
  css`
47
99
  :host([focus-ring]) [part='thumb'] {
48
- outline: var(--vaadin-focus-ring-width) solid var(--vaadin-focus-ring-color);
100
+ outline: var(--vaadin-focus-ring-width) var(--_outline-style, solid) var(--vaadin-focus-ring-color);
49
101
  outline-offset: 1px;
50
102
  }
51
103
 
52
- :host([readonly][focus-ring]) [part~='thumb'] {
53
- outline-style: dashed;
104
+ #controls {
105
+ grid-template-columns:
106
+ [track-start fill-start]
107
+ calc(var(--value) * var(--_track-width))
108
+ [fill-end thumb1]
109
+ var(--_thumb-width)
110
+ calc((1 - var(--value)) * var(--_track-width))
111
+ [track-end];
112
+ }
113
+
114
+ [part='track-fill'] {
115
+ border-start-start-radius: inherit;
116
+ border-end-start-radius: inherit;
54
117
  }
55
118
  `,
56
119
  ];
@@ -60,6 +123,10 @@ class Slider extends SliderMixin(
60
123
  return 'sliderComponent';
61
124
  }
62
125
 
126
+ static get lumoInjector() {
127
+ return { ...super.lumoInjector, includeBaseStyles: true };
128
+ }
129
+
63
130
  static get properties() {
64
131
  return {
65
132
  /**
@@ -80,17 +147,28 @@ class Slider extends SliderMixin(
80
147
  const percent = this.__getPercentFromValue(value);
81
148
 
82
149
  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>
150
+ <div class="vaadin-slider-container">
151
+ <div part="label" @click="${this.focus}">
152
+ <slot name="label"></slot>
153
+ <span part="required-indicator" aria-hidden="true"></span>
154
+ </div>
155
+
156
+ <div id="controls" style="${styleMap({ '--value': percent })}">
157
+ <div part="track">
158
+ <div part="track-fill"></div>
159
+ </div>
160
+ <div part="thumb"></div>
161
+ <slot name="input"></slot>
162
+ </div>
163
+
164
+ <div part="helper-text">
165
+ <slot name="helper"></slot>
166
+ </div>
167
+
168
+ <div part="error-message">
169
+ <slot name="error-message"></slot>
170
+ </div>
91
171
  </div>
92
- <div part="thumb" style="${styleMap({ insetInlineStart: `${percent}%` })}"></div>
93
- <slot name="input"></slot>
94
172
  `;
95
173
  }
96
174
 
@@ -107,6 +185,7 @@ class Slider extends SliderMixin(
107
185
 
108
186
  const input = this.querySelector('[slot="input"]');
109
187
  this._inputElement = input;
188
+ this.ariaTarget = input;
110
189
  }
111
190
 
112
191
  /**
@@ -168,6 +247,16 @@ class Slider extends SliderMixin(
168
247
  super.focus(options);
169
248
  }
170
249
 
250
+ /**
251
+ * @protected
252
+ * @override
253
+ */
254
+ blur() {
255
+ if (this._inputElement) {
256
+ this._inputElement.blur();
257
+ }
258
+ }
259
+
171
260
  /**
172
261
  * @private
173
262
  * @override
@@ -176,6 +265,14 @@ class Slider extends SliderMixin(
176
265
  this.value = this.__value[0];
177
266
  }
178
267
 
268
+ /** @private */
269
+ __onInput(event) {
270
+ event.stopPropagation();
271
+ this.__updateValue(event.target.value, 0);
272
+ this.__dispatchInputEvent();
273
+ this.__commitValue();
274
+ }
275
+
179
276
  /** @private */
180
277
  __onKeyDown(event) {
181
278
  const arrowKeys = ['ArrowLeft', 'ArrowDown', 'ArrowRight', 'ArrowUp'];