Skip to content

Commit 492dce8

Browse files
committed
feat(TimePicker): add support for arrow keys when selecting time parts
1 parent 3b357ca commit 492dce8

File tree

3 files changed

+91
-5
lines changed

3 files changed

+91
-5
lines changed

js/src/time-picker.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import BaseComponent from './base-component.js'
1010
import EventHandler from './dom/event-handler.js'
1111
import Manipulator from './dom/manipulator.js'
1212
import SelectorEngine from './dom/selector-engine.js'
13-
import { defineJQueryPlugin, getElement, isRTL } from './util/index.js'
13+
import {
14+
defineJQueryPlugin, getElement, getNextActiveElement, isRTL
15+
} from './util/index.js'
1416
import {
1517
convert12hTo24h,
1618
convert24hTo12h,
@@ -33,9 +35,14 @@ const ENTER_KEY = 'Enter'
3335
const ESCAPE_KEY = 'Escape'
3436
const SPACE_KEY = 'Space'
3537
const TAB_KEY = 'Tab'
38+
const ARROW_UP_KEY = 'ArrowUp'
39+
const ARROW_DOWN_KEY = 'ArrowDown'
40+
const ARROW_LEFT_KEY = 'ArrowLeft'
41+
const ARROW_RIGHT_KEY = 'ArrowRight'
3642
const RIGHT_MOUSE_BUTTON = 2
3743

3844
const EVENT_CLICK = `click${EVENT_KEY}`
45+
const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`
3946
const EVENT_HIDE = `hide${EVENT_KEY}`
4047
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
4148
const EVENT_INPUT = 'input'
@@ -71,6 +78,9 @@ const CLASS_NAME_WAS_VALIDATED = 'was-validated'
7178
const SELECTOR_DATA_TOGGLE =
7279
'[data-coreui-toggle="time-picker"]:not(.disabled):not(:disabled)'
7380
const SELECTOR_DATA_TOGGLE_SHOWN = `${SELECTOR_DATA_TOGGLE}.${CLASS_NAME_SHOW}`
81+
const SELECTOR_ROLL_CELL = '.time-picker-roll-cell'
82+
const SELECTOR_ROLL_CELL_SELECTED = '.time-picker-roll-cell.selected'
83+
const SELECTOR_ROLL_COL = '.time-picker-roll-col'
7484
const SELECTOR_WAS_VALIDATED = 'form.was-validated'
7585

7686
const Default = {
@@ -294,6 +304,58 @@ class TimePicker extends BaseComponent {
294304
}
295305
})
296306

307+
if (this._config.variant === 'roll') {
308+
EventHandler.on(this._timePickerBody, EVENT_FOCUSOUT, SELECTOR_ROLL_COL, () => {
309+
this._setUpRolls(false)
310+
})
311+
312+
EventHandler.on(this._timePickerBody, EVENT_KEYDOWN, SELECTOR_ROLL_CELL, event => {
313+
if (event.key === ARROW_DOWN_KEY || event.key === ARROW_UP_KEY) {
314+
event.preventDefault()
315+
const { key, target } = event
316+
const items = SelectorEngine.find(SELECTOR_ROLL_CELL, target.parentElement)
317+
318+
if (!items.length) {
319+
return
320+
}
321+
322+
getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus()
323+
}
324+
325+
if (event.key === ARROW_LEFT_KEY || event.key === ARROW_RIGHT_KEY) {
326+
event.preventDefault()
327+
const { key, target } = event
328+
const columnElement = target.parentElement
329+
330+
if (this._timePickerBody) {
331+
const columns = SelectorEngine.find(SELECTOR_ROLL_COL, this._timePickerBody)
332+
const currentColumnIndex = columns.indexOf(columnElement)
333+
334+
let targetColumnIndex
335+
const isRtl = isRTL()
336+
const shouldGoLeft = (key === ARROW_LEFT_KEY && !isRtl) || (key === ARROW_RIGHT_KEY && isRtl)
337+
if (shouldGoLeft) {
338+
targetColumnIndex = currentColumnIndex > 0 ? currentColumnIndex - 1 : columns.length - 1
339+
} else {
340+
targetColumnIndex = currentColumnIndex < columns.length - 1 ? currentColumnIndex + 1 : 0
341+
}
342+
343+
const targetColumn = columns[targetColumnIndex]
344+
const selectedCell = SelectorEngine.findOne(SELECTOR_ROLL_CELL_SELECTED, targetColumn)
345+
346+
if (selectedCell) {
347+
selectedCell.focus()
348+
return
349+
}
350+
351+
const firstFocusableCell = SelectorEngine.findOne(SELECTOR_ROLL_CELL, targetColumn)
352+
353+
firstFocusableCell.focus()
354+
}
355+
}
356+
})
357+
}
358+
297359
EventHandler.on(this._element, EVENT_KEYDOWN, event => {
298360
if (event.key === ESCAPE_KEY) {
299361
this.hide()

scss/_time-picker.scss

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,12 @@
5151
--#{$prefix}time-picker-dropdown-box-shadow: #{$time-picker-dropdown-box-shadow};
5252
--#{$prefix}time-picker-roll-col-border-width: #{$time-picker-roll-col-border-width};
5353
--#{$prefix}time-picker-roll-col-border-color: #{$time-picker-roll-col-border-color};
54+
--#{$prefix}time-picker-roll-cell-hover-color: #{$time-picker-roll-cell-hover-color};
55+
--#{$prefix}time-picker-roll-cell-hover-bg: #{$time-picker-roll-cell-hover-bg};
5456
--#{$prefix}time-picker-roll-cell-selected-color: #{$time-picker-roll-cell-selected-color};
5557
--#{$prefix}time-picker-roll-cell-selected-bg: #{$time-picker-roll-cell-selected-bg};
58+
--#{$prefix}time-picker-roll-cell-focus-box-shadow: #{$time-picker-roll-cell-focus-box-shadow};
59+
--#{$prefix}time-picker-roll-cell-selected-focus-box-shadow: #{$time-picker-roll-cell-selected-focus-box-shadow};
5660
--#{$prefix}time-picker-inline-select-font-size: #{$time-picker-inline-select-font-size};
5761
--#{$prefix}time-picker-inline-select-color: #{$time-picker-inline-select-color};
5862
--#{$prefix}time-picker-inline-select-padding-y: #{$time-picker-inline-select-padding-y};
@@ -293,6 +297,7 @@
293297

294298
-ms-overflow-style: none; /* Internet Explorer 10+ */
295299
scrollbar-width: none; /* Firefox */
300+
scroll-behavior: smooth;
296301

297302
&::-webkit-scrollbar {
298303
display: none; /* Safari and Chrome */
@@ -309,9 +314,24 @@
309314
padding-inline: $spacer * .5 $spacer;
310315
line-height: 32px;
311316

317+
&:hover,
318+
&:focus-visible:not(.selected) {
319+
color: var(--#{$prefix}time-picker-roll-cell-hover-color);
320+
background: var(--#{$prefix}time-picker-roll-cell-hover-bg);
321+
}
322+
312323
&.selected {
313324
color: var(--#{$prefix}time-picker-roll-cell-selected-color);
314325
background: var(--#{$prefix}time-picker-roll-cell-selected-bg);
326+
327+
&:focus-visible {
328+
box-shadow: var(--#{$prefix}time-picker-roll-cell-selected-focus-box-shadow);
329+
}
330+
}
331+
332+
&:focus-visible {
333+
outline: 0;
334+
box-shadow: var(--#{$prefix}time-picker-roll-cell-focus-box-shadow);
315335
}
316336

317337
&:last-child::after {

scss/_variables.scss

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,10 +2684,14 @@ $time-picker-footer-padding: .5rem !default;
26842684
$time-picker-footer-border-width: 1px !default;
26852685
$time-picker-footer-border-color: var(--#{$prefix}border-color) !default;
26862686

2687-
$time-picker-roll-col-border-width: var(--#{$prefix}border-width) !default;
2688-
$time-picker-roll-col-border-color: var(--#{$prefix}border-color) !default;
2689-
$time-picker-roll-cell-selected-color: var(--#{$prefix}white) !default;
2690-
$time-picker-roll-cell-selected-bg: var(--#{$prefix}primary) !default;
2687+
$time-picker-roll-col-border-width: var(--#{$prefix}border-width) !default;
2688+
$time-picker-roll-col-border-color: var(--#{$prefix}border-color) !default;
2689+
$time-picker-roll-cell-hover-color: var(--#{$prefix}body-color) !default;
2690+
$time-picker-roll-cell-hover-bg: var(--#{$prefix}tertiary-bg) !default;
2691+
$time-picker-roll-cell-selected-color: var(--#{$prefix}white) !default;
2692+
$time-picker-roll-cell-selected-bg: var(--#{$prefix}primary) !default;
2693+
$time-picker-roll-cell-focus-box-shadow: inset 0 0 $focus-ring-blur .125rem $focus-ring-color !default;
2694+
$time-picker-roll-cell-selected-focus-box-shadow: inset 0 0 $focus-ring-blur .125rem rgba($white, .75) !default;
26912695

26922696
$time-picker-inline-select-font-size: $form-select-font-size-sm !default;
26932697
$time-picker-inline-select-color: $input-color !default;

0 commit comments

Comments
 (0)