|
1 | 1 | import Float from "@ui5/webcomponents-base/dist/types/Float.js";
|
2 | 2 | import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
|
3 | 3 | import SliderBase from "./SliderBase.js";
|
| 4 | +import {isEscape, isTabPrevious, isTabNext, isHome, isEnd} from "@ui5/webcomponents-base/dist/Keys.js"; |
4 | 5 |
|
5 | 6 | // Template
|
6 | 7 | import RangeSliderTemplate from "./generated/templates/RangeSliderTemplate.lit.js";
|
@@ -99,6 +100,12 @@ class RangeSlider extends SliderBase {
|
99 | 100 | this.i18nBundle = getI18nBundle("@ui5/webcomponents");
|
100 | 101 | }
|
101 | 102 |
|
| 103 | + onEnterDOM() { |
| 104 | + this._sliderStartHandle = this.shadowRoot.querySelector(".ui5-slider-handle--start"); |
| 105 | + this._sliderEndHandle = this.shadowRoot.querySelector(".ui5-slider-handle--end"); |
| 106 | + this._sliderProgress = this.shadowRoot.querySelector(".ui5-slider-progress"); |
| 107 | + } |
| 108 | + |
102 | 109 | get tooltipStartValue() {
|
103 | 110 | const stepPrecision = this.constructor._getDecimalPrecisionOfNumber(this._effectiveStep);
|
104 | 111 | return this.startValue.toFixed(stepPrecision);
|
@@ -129,6 +136,103 @@ class RangeSlider extends SliderBase {
|
129 | 136 | this._updateHandlesAndRange(null);
|
130 | 137 | }
|
131 | 138 |
|
| 139 | + _onfocusin(event) { |
| 140 | + this.focused = true; |
| 141 | + |
| 142 | + this._setInitialValue("startValue", this._prevStartValue || this.startValue); |
| 143 | + this._setInitialValue("endValue", this._prevEndValue || this.endValue); |
| 144 | + } |
| 145 | + |
| 146 | + _onfocusout(event) { |
| 147 | + if (this._isFocusing()) { |
| 148 | + // Prevent focusout when the focus is getting initially set within the slider before the |
| 149 | + // slider customElement itself is finished focusing. |
| 150 | + this._preventFocusOut(); |
| 151 | + } else { |
| 152 | + this.focused = false; |
| 153 | + this._valueAffected = null; |
| 154 | + |
| 155 | + // Reset the stored Slider's initial value saved when it was first focused |
| 156 | + this._setInitialValue("value", null); |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + _preventFocusOut() { |
| 161 | + this._focusInnerElement(); |
| 162 | + } |
| 163 | + |
| 164 | + _onkeydown(event) { |
| 165 | + this._onKeyDownBase(event); |
| 166 | + } |
| 167 | + |
| 168 | + _handleActionKeyPress(event) { |
| 169 | + if (isEscape(event)) { |
| 170 | + this.startValue = this._prevStartValue; |
| 171 | + this.endValue = this._prevEndValue; |
| 172 | + } |
| 173 | + |
| 174 | + |
| 175 | + if (this.shadowRoot.activeElement === this._sliderStartHandle) { |
| 176 | + this._valueAffected = "startValue" |
| 177 | + } |
| 178 | + |
| 179 | + if (this.shadowRoot.activeElement === this._sliderEndHandle) { |
| 180 | + this._valueAffected = "endValue" |
| 181 | + } |
| 182 | + |
| 183 | + const min = this._effectiveMin; |
| 184 | + const max = this._effectiveMax; |
| 185 | + const valueType = this._valueAffected; |
| 186 | + |
| 187 | + if ((isEnd(event) || isHome(event)) && !valueType) { |
| 188 | + const eventType = isHome(event) ? "home" : "end" |
| 189 | + this._handleHomeEndKeys(event, eventType, min, max) |
| 190 | + |
| 191 | + return; |
| 192 | + } |
| 193 | + |
| 194 | + if (valueType) { |
| 195 | + const newValueOffset = SliderBase.prototype._handleActionKeyPress.call(this, event, valueType); |
| 196 | + |
| 197 | + if (!newValueOffset) { |
| 198 | + return; |
| 199 | + } |
| 200 | + |
| 201 | + const newValue = isEscape(event) ? this._getInitialValue(valueType) : this.constructor.clipValue(newValueOffset + currentValue, min, max); |
| 202 | + const currentValue = this[valueType]; |
| 203 | + |
| 204 | + this._updateHandlesAndRange(newValue); |
| 205 | + this.updateValue(valueType, newValue); |
| 206 | + this.storePropertyState(valueType); |
| 207 | + } else { |
| 208 | + const newValueOffset = SliderBase.prototype._handleActionKeyPress.call(this, event, null); |
| 209 | + |
| 210 | + if (!newValueOffset) { |
| 211 | + return; |
| 212 | + } |
| 213 | + |
| 214 | + const newStartValue = isEscape(event) ? this._getInitialValue("startValue") : this.constructor.clipValue(newValueOffset + this.startValue, min, max); |
| 215 | + const newEndValue = isEscape(event) ? this._getInitialValue("endValue") : this.constructor.clipValue(newValueOffset + this.endValue, min, max); |
| 216 | + |
| 217 | + this.updateValue("startValue", newStartValue); |
| 218 | + this.updateValue("endValue", newEndValue); |
| 219 | + this._updateHandlesAndRange(null); |
| 220 | + this.storePropertyState("startValue", "endValue"); |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | + _handleHomeEndKeys(event, eventType, min, max) { |
| 225 | + const affectedValue = eventType === "home" ? "startValue" : "endValue"; |
| 226 | + const newValueOffset = SliderBase.prototype._handleActionKeyPress.call(this, event, affectedValue) |
| 227 | + const newStartValue = this.constructor.clipValue(newValueOffset + this.startValue, min, max); |
| 228 | + const newEndValue = this.constructor.clipValue(newValueOffset + this.endValue, min, max); |
| 229 | + |
| 230 | + this.updateValue("startValue", newStartValue); |
| 231 | + this.updateValue("endValue", newEndValue); |
| 232 | + this._updateHandlesAndRange(null); |
| 233 | + this.storePropertyState("startValue", "endValue"); |
| 234 | + } |
| 235 | + |
132 | 236 | /**
|
133 | 237 | * Called when the user starts interacting with the slider
|
134 | 238 | *
|
@@ -254,9 +358,9 @@ class RangeSlider extends SliderBase {
|
254 | 358 | this._swapValues();
|
255 | 359 | this.handleUpBase();
|
256 | 360 |
|
257 |
| - this._valueAffected = null; |
258 | 361 | this._prevStartValue = null;
|
259 | 362 | this._prevEndValue = null;
|
| 363 | + this._inCurrentRange = null; |
260 | 364 | }
|
261 | 365 |
|
262 | 366 | /**
|
@@ -300,6 +404,37 @@ class RangeSlider extends SliderBase {
|
300 | 404 | }
|
301 | 405 | }
|
302 | 406 |
|
| 407 | + /* Flag the component that it is currently being in process of focusing in. When the slider is getting focused |
| 408 | + we need that focus to be delegated to the Slider's handle and to not stay on the slider's shadow root div, as it |
| 409 | + is by default. In theory this can be achieved either if the 'delegatesFocus' attribute of the .attachShadow() |
| 410 | + customElement method is set to true or if we forward it manually as part of the component logic. |
| 411 | + |
| 412 | + As we use lit-element as base of our core UI5 element class that 'delegatesFocus' property is not set to 'true' and |
| 413 | + we have to manage the focus here. If at some point in the future this changes, the focus delegating logic could be |
| 414 | + removed as it will become redundant. |
| 415 | + |
| 416 | + When we manually set the focus on mouseDown to the first focusable element inside the shadowDom - the slider's handle, |
| 417 | + that inside focus and subsquently the shadowRoot.activeElement are set a moment before the global document.activeElement |
| 418 | + is set to the customElement (ui5-slider) causing a 'race condition'. |
| 419 | + |
| 420 | + In order for a element within the shadowRoot to be focused, the global document.activeElement MUST be the parent |
| 421 | + customElement of the shadow root, in our case the ui5-slider component. Because of that after our focusin of the handle, |
| 422 | + a focusout event fired by the browser immidiatly after, resetting the focus. |
| 423 | + |
| 424 | + Note: If we set the focus to the handle a bit later in time, for example on a mouseup or click event it will |
| 425 | + work fine and we will avoid the described race condition as our customElement will be already finished focusing. |
| 426 | + However, that does not work for us as we need the focus to be set to the handle exactly on mousedown, |
| 427 | + because of the nature of the component and its available drag interactions.*/ |
| 428 | + _focusInnerElement() { |
| 429 | + if (this._inCurrentRange) { |
| 430 | + this._sliderProgress.focus(); |
| 431 | + } else if (this._valueAffected === "startValue") { |
| 432 | + this._sliderStartHandle.focus(); |
| 433 | + } else { |
| 434 | + this._sliderEndHandle.focus(); |
| 435 | + } |
| 436 | + } |
| 437 | + |
303 | 438 | /**
|
304 | 439 | * Calculates startValue/endValue properties when the whole range is moved.
|
305 | 440 | *
|
@@ -419,6 +554,10 @@ class RangeSlider extends SliderBase {
|
419 | 554 | }
|
420 | 555 | }
|
421 | 556 |
|
| 557 | + get tabIndexProgress() { |
| 558 | + return this.tabIndex; |
| 559 | + } |
| 560 | + |
422 | 561 | get styles() {
|
423 | 562 | return {
|
424 | 563 | progress: {
|
|
0 commit comments