@@ -4,7 +4,9 @@ import Float from "@ui5/webcomponents-base/dist/types/Float.js";
4
4
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js" ;
5
5
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js" ;
6
6
import { isPhone } from "@ui5/webcomponents-base/dist/Device.js" ;
7
- import { isEscape , isHome , isEnd , isUp , isDown , isRight , isLeft , isUpCtrl , isDownCtrl , isRightCtrl , isLeftCtrl , isPlus , isMinus , isPageUp , isPageDown , getCtrlKey } from "@ui5/webcomponents-base/dist/Keys.js" ;
7
+ import {
8
+ isEscape , isHome , isEnd , isUp , isDown , isRight , isLeft , isUpCtrl , isDownCtrl , isRightCtrl , isLeftCtrl , isPlus , isMinus , isPageUp , isPageDown ,
9
+ } from "@ui5/webcomponents-base/dist/Keys.js" ;
8
10
import { getTheme } from "@ui5/webcomponents-base/dist/config/Theme.js" ;
9
11
10
12
// Styles
@@ -292,21 +294,27 @@ class SliderBase extends UI5Element {
292
294
}
293
295
}
294
296
297
+ /**
298
+ * Sets initial value when the component is focused in, can be restored with ESC key
299
+ *
300
+ * @private
301
+ */
295
302
_setInitialValue ( valueType , value ) {
296
303
this [ `_${ valueType } Initial` ] = value ;
297
- }
304
+ }
298
305
299
306
_getInitialValue ( valueType ) {
300
307
return this [ `_${ valueType } Initial` ] ;
301
308
}
302
309
303
- _onKeyDownBase ( event ) {
310
+ _handleKeyDown ( event ) {
304
311
if ( this . disabled ) {
305
312
return ;
306
313
}
307
314
308
315
if ( SliderBase . _isActionKey ( event ) ) {
309
316
event . preventDefault ( ) ;
317
+
310
318
this . _isUserInteraction = true ;
311
319
this . _handleActionKeyPress ( event ) ;
312
320
}
@@ -320,18 +328,52 @@ class SliderBase extends UI5Element {
320
328
this . _isUserInteraction = false ;
321
329
}
322
330
323
- static _isActionKey ( event ) {
324
- return this . ACTION_KEYS . some ( actionKey => actionKey ( event ) ) ;
331
+ /**
332
+ * Flags if an inner element is currently being focused
333
+ *
334
+ * @private
335
+ */
336
+ _preserveFocus ( isFocusing ) {
337
+ this . _isInnerElementFocusing = isFocusing ;
325
338
}
326
-
339
+
340
+ /**
341
+ * Return if an inside element within the component is currently being focused
342
+ *
343
+ * @private
344
+ */
327
345
_isFocusing ( ) {
328
- return this . _isInProcessOfFocusing ;
346
+ return this . _isInnerElementFocusing ;
329
347
}
330
348
331
- _setIsFocusing ( isInProcessOfFocusing ) {
332
- this . _isInProcessOfFocusing = isInProcessOfFocusing ;
333
- }
349
+ /**
350
+ * Prevent focus out when inner element within the component is currently being in process of focusing in.
351
+ * In theory this can be achieved either if the shadow root is focusable and 'delegatesFocus' attribute of
352
+ * the .attachShadow() customElement method is set to true, or if we forward it manually.
334
353
354
+ * As we use lit-element as base of our core UI5 element class that 'delegatesFocus' property is not set to 'true' and
355
+ * we have to manage the focus here. If at some point in the future this changes, the focus delegating logic could be
356
+ * removed as it will become redundant.
357
+ *
358
+ * When we manually set the focus on mouseDown to the first focusable element inside the shadowDom,
359
+ * that inner focus (shadowRoot.activeElement) is set a moment before the global document.activeElement
360
+ * is set to the customElement (ui5-slider) causing a 'race condition'.
361
+ *
362
+ * In order for a element within the shadowRoot to be focused, the global document.activeElement MUST be the parent
363
+ * customElement of the shadow root, in our case the ui5-slider component. Because of that after our focusin of the handle,
364
+ * a focusout event fired by the browser immidiatly after, resetting the focus. Focus out must be manually prevented
365
+ * in both initial focusing and switching the focus between inner elements of the component cases.
366
+
367
+ * Note: If we set the focus to the handle with a timeout or a bit later in time, on a mouseup or click event it will
368
+ * work fine and we will avoid the described race condition as our host customElement will be already finished focusing.
369
+ * However, that does not work for us as we need the focus to be set to the handle exactly on mousedown,
370
+ * because of the nature of the component and its available drag interactions.
371
+ *
372
+ * @private
373
+ */
374
+ _preventFocusOut ( ) {
375
+ this . _focusInnerElement ( ) ;
376
+ }
335
377
336
378
/**
337
379
* Handle the responsiveness of the Slider's UI elements when resizing
@@ -363,7 +405,6 @@ class SliderBase extends UI5Element {
363
405
return ;
364
406
}
365
407
366
-
367
408
// Check if there are any overlapping labels.
368
409
// If so - only the first and the last one should be visible
369
410
const labelItems = this . shadowRoot . querySelectorAll ( ".ui5-slider-labels li" ) ;
@@ -397,11 +438,24 @@ class SliderBase extends UI5Element {
397
438
SliderBase . UP_EVENTS . forEach ( upEventType => window . addEventListener ( upEventType , this . _upHandler ) ) ;
398
439
window . addEventListener ( this . _moveEventType , this . _moveHandler ) ;
399
440
400
- this . _setIsFocusing ( true ) ;
401
- this . _focusInnerElement ( ) ;
441
+ this . _handleFocusOnMouseDown ( event ) ;
402
442
return newValue ;
403
443
}
404
444
445
+ /**
446
+ * Forward the focus to an inner inner part within the component on press
447
+ *
448
+ * @private
449
+ */
450
+ _handleFocusOnMouseDown ( event ) {
451
+ const focusedElement = this . shadowRoot . activeElement ;
452
+
453
+ if ( ! focusedElement || focusedElement !== event . target ) {
454
+ this . _preserveFocus ( true ) ;
455
+ this . _focusInnerElement ( ) ;
456
+ }
457
+ }
458
+
405
459
/**
406
460
* Called when the user finish interacting with the slider
407
461
* Fires an <code>change</code> event indicating a final value change, after user interaction is finished.
@@ -418,7 +472,7 @@ class SliderBase extends UI5Element {
418
472
419
473
this . _moveEventType = null ;
420
474
this . _isUserInteraction = false ;
421
- this . _setIsFocusing ( false ) ;
475
+ this . _preserveFocus ( false ) ;
422
476
}
423
477
424
478
/**
@@ -435,6 +489,15 @@ class SliderBase extends UI5Element {
435
489
}
436
490
}
437
491
492
+ /**
493
+ * Goes through the key shortcuts available for the component and returns 'true' if the event is triggered by one.
494
+ *
495
+ * @private
496
+ */
497
+ static _isActionKey ( event ) {
498
+ return this . ACTION_KEYS . some ( actionKey => actionKey ( event ) ) ;
499
+ }
500
+
438
501
/**
439
502
* Locks the given value between min and max boundaries based on slider properties
440
503
*
@@ -688,7 +751,6 @@ class SliderBase extends UI5Element {
688
751
}
689
752
690
753
_handleActionKeyPress ( event , affectedValue ) {
691
- const isDownAction = SliderBase . _isDecreaseValueAction ( event ) ;
692
754
const isUpAction = SliderBase . _isIncreaseValueAction ( event ) ;
693
755
const isBigStep = SliderBase . _isBigStepAction ( event ) ;
694
756
@@ -707,7 +769,7 @@ class SliderBase extends UI5Element {
707
769
}
708
770
709
771
if ( isHome ( event ) ) {
710
- return ( currentValue - min ) * - 1 ;
772
+ return ( currentValue - min ) * - 1 ;
711
773
}
712
774
713
775
return isUpAction ? step : step * - 1 ;
0 commit comments