@coreui/vue-pro 5.15.0 → 5.16.0
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.
- package/README.md +1 -1
- package/dist/cjs/components/calendar/CCalendar.js +6 -0
- package/dist/cjs/components/calendar/CCalendar.js.map +1 -1
- package/dist/cjs/components/dropdown/CDropdown.d.ts +32 -7
- package/dist/cjs/components/dropdown/CDropdown.js +47 -18
- package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
- package/dist/cjs/components/dropdown/CDropdownToggle.d.ts +19 -0
- package/dist/cjs/components/dropdown/CDropdownToggle.js +10 -1
- package/dist/cjs/components/dropdown/CDropdownToggle.js.map +1 -1
- package/dist/cjs/components/dropdown/utils.d.ts +2 -0
- package/dist/cjs/components/dropdown/utils.js +13 -0
- package/dist/cjs/components/dropdown/utils.js.map +1 -1
- package/dist/cjs/components/nav/CNavItem.d.ts +2 -2
- package/dist/cjs/components/time-picker/CTimePicker.d.ts +44 -0
- package/dist/cjs/components/time-picker/CTimePicker.js +63 -7
- package/dist/cjs/components/time-picker/CTimePicker.js.map +1 -1
- package/dist/cjs/components/time-picker/CTimePickerRollCol.d.ts +19 -7
- package/dist/cjs/components/time-picker/CTimePickerRollCol.js +80 -8
- package/dist/cjs/components/time-picker/CTimePickerRollCol.js.map +1 -1
- package/dist/esm/components/calendar/CCalendar.js +6 -0
- package/dist/esm/components/calendar/CCalendar.js.map +1 -1
- package/dist/esm/components/dropdown/CDropdown.d.ts +32 -7
- package/dist/esm/components/dropdown/CDropdown.js +49 -20
- package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
- package/dist/esm/components/dropdown/CDropdownToggle.d.ts +19 -0
- package/dist/esm/components/dropdown/CDropdownToggle.js +10 -1
- package/dist/esm/components/dropdown/CDropdownToggle.js.map +1 -1
- package/dist/esm/components/dropdown/utils.d.ts +2 -0
- package/dist/esm/components/dropdown/utils.js +13 -1
- package/dist/esm/components/dropdown/utils.js.map +1 -1
- package/dist/esm/components/nav/CNavItem.d.ts +2 -2
- package/dist/esm/components/time-picker/CTimePicker.d.ts +44 -0
- package/dist/esm/components/time-picker/CTimePicker.js +63 -7
- package/dist/esm/components/time-picker/CTimePicker.js.map +1 -1
- package/dist/esm/components/time-picker/CTimePickerRollCol.d.ts +19 -7
- package/dist/esm/components/time-picker/CTimePickerRollCol.js +80 -8
- package/dist/esm/components/time-picker/CTimePickerRollCol.js.map +1 -1
- package/package.json +3 -3
- package/src/components/calendar/CCalendar.ts +6 -0
- package/src/components/dropdown/CDropdown.ts +92 -36
- package/src/components/dropdown/CDropdownToggle.ts +10 -1
- package/src/components/dropdown/utils.ts +21 -0
- package/src/components/nav/CNavItem.ts +1 -1
- package/src/components/time-picker/CTimePicker.ts +68 -9
- package/src/components/time-picker/CTimePickerRollCol.ts +87 -9
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
defineComponent,
|
|
4
|
+
h,
|
|
5
|
+
nextTick,
|
|
6
|
+
onUnmounted,
|
|
7
|
+
provide,
|
|
8
|
+
PropType,
|
|
9
|
+
ref,
|
|
10
|
+
Ref,
|
|
11
|
+
watch,
|
|
12
|
+
} from 'vue'
|
|
2
13
|
import type { Placement } from '@popperjs/core'
|
|
3
14
|
|
|
4
15
|
import { usePopper } from '../../composables'
|
|
@@ -6,7 +17,8 @@ import type { Triggers } from '../../types'
|
|
|
6
17
|
import { getNextActiveElement, isRTL } from '../../utils'
|
|
7
18
|
|
|
8
19
|
import type { Alignments } from './types'
|
|
9
|
-
import { getPlacement } from './utils'
|
|
20
|
+
import { getPlacement, getReferenceElement } from './utils'
|
|
21
|
+
import { CFocusTrap } from '../focus-trap'
|
|
10
22
|
|
|
11
23
|
const CDropdown = defineComponent({
|
|
12
24
|
name: 'CDropdown',
|
|
@@ -53,7 +65,7 @@ const CDropdown = defineComponent({
|
|
|
53
65
|
* - `'outside'` - the dropdown will be closed (only) by clicking outside the dropdown menu.
|
|
54
66
|
*/
|
|
55
67
|
autoClose: {
|
|
56
|
-
type: [Boolean, String]
|
|
68
|
+
type: [Boolean, String] as PropType<boolean | 'inside' | 'outside'>,
|
|
57
69
|
default: true,
|
|
58
70
|
validator: (value: boolean | string) => {
|
|
59
71
|
return typeof value === 'boolean' || ['inside', 'outside'].includes(value)
|
|
@@ -112,6 +124,21 @@ const CDropdown = defineComponent({
|
|
|
112
124
|
type: Boolean,
|
|
113
125
|
default: true,
|
|
114
126
|
},
|
|
127
|
+
/**
|
|
128
|
+
* Sets the reference element for positioning the Vue Dropdown Menu.
|
|
129
|
+
* - `toggle` - The Vue Dropdown Toggle button (default).
|
|
130
|
+
* - `parent` - The Vue Dropdown wrapper element.
|
|
131
|
+
* - `HTMLElement` - A custom HTML element.
|
|
132
|
+
* - `Ref` - A custom reference element.
|
|
133
|
+
*
|
|
134
|
+
* @since 5.7.0
|
|
135
|
+
*/
|
|
136
|
+
reference: {
|
|
137
|
+
type: [String, Object] as PropType<
|
|
138
|
+
'parent' | 'toggle' | HTMLElement | Ref<HTMLElement | null>
|
|
139
|
+
>,
|
|
140
|
+
default: 'toggle',
|
|
141
|
+
},
|
|
115
142
|
/**
|
|
116
143
|
* Generates dropdown menu using Teleport.
|
|
117
144
|
*
|
|
@@ -156,15 +183,16 @@ const CDropdown = defineComponent({
|
|
|
156
183
|
'show',
|
|
157
184
|
],
|
|
158
185
|
setup(props, { slots, emit }) {
|
|
159
|
-
const
|
|
160
|
-
const dropdownMenuRef = ref()
|
|
186
|
+
const dropdownRef = ref<HTMLElement | null>(null)
|
|
187
|
+
const dropdownMenuRef = ref<HTMLElement | null>(null)
|
|
188
|
+
const dropdownToggleRef = ref<HTMLElement | null>(null)
|
|
161
189
|
const pendingKeyDownEventRef = ref<KeyboardEvent | null>(null)
|
|
162
190
|
const popper = ref(typeof props.alignment === 'object' ? false : props.popper)
|
|
163
191
|
const visible = ref(props.visible)
|
|
164
192
|
|
|
165
193
|
const { initPopper, destroyPopper } = usePopper()
|
|
166
194
|
|
|
167
|
-
const popperConfig = {
|
|
195
|
+
const popperConfig = computed(() => ({
|
|
168
196
|
modifiers: [
|
|
169
197
|
{
|
|
170
198
|
name: 'offset',
|
|
@@ -179,7 +207,7 @@ const CDropdown = defineComponent({
|
|
|
179
207
|
props.alignment,
|
|
180
208
|
isRTL(dropdownMenuRef.value)
|
|
181
209
|
) as Placement,
|
|
182
|
-
}
|
|
210
|
+
}))
|
|
183
211
|
|
|
184
212
|
watch(
|
|
185
213
|
() => props.visible,
|
|
@@ -190,11 +218,16 @@ const CDropdown = defineComponent({
|
|
|
190
218
|
|
|
191
219
|
watch(visible, () => {
|
|
192
220
|
if (visible.value && dropdownToggleRef.value && dropdownMenuRef.value) {
|
|
193
|
-
|
|
194
|
-
|
|
221
|
+
const referenceElement = getReferenceElement(
|
|
222
|
+
props.reference,
|
|
223
|
+
dropdownToggleRef,
|
|
224
|
+
dropdownRef
|
|
225
|
+
)
|
|
226
|
+
if (referenceElement && popper.value) {
|
|
227
|
+
initPopper(referenceElement, dropdownMenuRef.value, popperConfig.value)
|
|
195
228
|
}
|
|
196
229
|
|
|
197
|
-
window.addEventListener('
|
|
230
|
+
window.addEventListener('click', handleClick)
|
|
198
231
|
window.addEventListener('keyup', handleKeyup)
|
|
199
232
|
dropdownToggleRef.value.addEventListener('keydown', handleKeydown)
|
|
200
233
|
dropdownMenuRef.value.addEventListener('keydown', handleKeydown)
|
|
@@ -205,15 +238,20 @@ const CDropdown = defineComponent({
|
|
|
205
238
|
pendingKeyDownEventRef.value = null
|
|
206
239
|
})
|
|
207
240
|
}
|
|
208
|
-
|
|
241
|
+
|
|
209
242
|
emit('show')
|
|
210
243
|
return
|
|
211
244
|
}
|
|
212
245
|
|
|
213
|
-
popper.value
|
|
214
|
-
|
|
246
|
+
if (popper.value) {
|
|
247
|
+
destroyPopper()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
window.removeEventListener('click', handleClick)
|
|
215
251
|
window.removeEventListener('keyup', handleKeyup)
|
|
216
252
|
dropdownMenuRef.value && dropdownMenuRef.value.removeEventListener('keydown', handleKeydown)
|
|
253
|
+
dropdownToggleRef.value &&
|
|
254
|
+
dropdownToggleRef.value.removeEventListener('keydown', handleKeydown)
|
|
217
255
|
emit('hide')
|
|
218
256
|
})
|
|
219
257
|
|
|
@@ -259,24 +297,36 @@ const CDropdown = defineComponent({
|
|
|
259
297
|
}
|
|
260
298
|
}
|
|
261
299
|
|
|
262
|
-
const
|
|
300
|
+
const handleClick = (event: Event) => {
|
|
263
301
|
if (!dropdownToggleRef.value || !dropdownMenuRef.value) {
|
|
264
302
|
return
|
|
265
303
|
}
|
|
266
304
|
|
|
267
|
-
if (
|
|
305
|
+
if ((event as MouseEvent).button === 2) {
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const composedPath = event.composedPath()
|
|
310
|
+
const isOnToggle = composedPath.includes(dropdownToggleRef.value)
|
|
311
|
+
const isOnMenu = composedPath.includes(dropdownMenuRef.value)
|
|
312
|
+
|
|
313
|
+
if (isOnToggle) {
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const target = event.target as HTMLElement | null
|
|
318
|
+
const FORM_TAG_RE = /^(input|select|option|textarea|form|button|label)$/i
|
|
319
|
+
|
|
320
|
+
if (isOnMenu && target && FORM_TAG_RE.test(target.tagName)) {
|
|
268
321
|
return
|
|
269
322
|
}
|
|
270
323
|
|
|
271
324
|
if (
|
|
272
325
|
props.autoClose === true ||
|
|
273
|
-
(props.autoClose === 'inside' &&
|
|
274
|
-
|
|
275
|
-
(props.autoClose === 'outside' &&
|
|
276
|
-
!dropdownMenuRef.value.contains(event.target as HTMLElement))
|
|
326
|
+
(props.autoClose === 'inside' && isOnMenu) ||
|
|
327
|
+
(props.autoClose === 'outside' && !isOnMenu)
|
|
277
328
|
) {
|
|
278
329
|
setVisible(false)
|
|
279
|
-
return
|
|
280
330
|
}
|
|
281
331
|
}
|
|
282
332
|
|
|
@@ -299,22 +349,28 @@ const CDropdown = defineComponent({
|
|
|
299
349
|
provide('setVisible', setVisible)
|
|
300
350
|
|
|
301
351
|
return () =>
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
:
|
|
312
|
-
? '
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
352
|
+
h(
|
|
353
|
+
CFocusTrap,
|
|
354
|
+
{ active: props.teleport && visible.value, additionalContainer: dropdownMenuRef },
|
|
355
|
+
() =>
|
|
356
|
+
props.variant === 'input-group'
|
|
357
|
+
? [slots.default && slots.default()]
|
|
358
|
+
: h(
|
|
359
|
+
'div',
|
|
360
|
+
{
|
|
361
|
+
class: [
|
|
362
|
+
props.variant === 'nav-item' ? 'nav-item dropdown' : props.variant,
|
|
363
|
+
props.direction === 'center'
|
|
364
|
+
? 'dropdown-center'
|
|
365
|
+
: props.direction === 'dropup-center'
|
|
366
|
+
? 'dropup dropup-center'
|
|
367
|
+
: props.direction,
|
|
368
|
+
],
|
|
369
|
+
ref: dropdownRef,
|
|
370
|
+
},
|
|
371
|
+
slots.default && slots.default()
|
|
372
|
+
)
|
|
373
|
+
)
|
|
318
374
|
},
|
|
319
375
|
})
|
|
320
376
|
|
|
@@ -74,6 +74,15 @@ const CDropdownToggle = defineComponent({
|
|
|
74
74
|
* Similarly, create split button dropdowns with virtually the same markup as single button dropdowns, but with the addition of `.dropdown-toggle-split` className for proper spacing around the dropdown caret.
|
|
75
75
|
*/
|
|
76
76
|
split: Boolean,
|
|
77
|
+
/**
|
|
78
|
+
* Screen reader label for split button dropdown toggle.
|
|
79
|
+
*
|
|
80
|
+
* @since 5.7.0
|
|
81
|
+
*/
|
|
82
|
+
splitLabel: {
|
|
83
|
+
type: String,
|
|
84
|
+
default: 'Toggle Dropdown',
|
|
85
|
+
},
|
|
77
86
|
/**
|
|
78
87
|
* Sets which event handlers you’d like provided to your toggle prop. You can specify one trigger or an array of them.
|
|
79
88
|
*
|
|
@@ -194,7 +203,7 @@ const CDropdownToggle = defineComponent({
|
|
|
194
203
|
},
|
|
195
204
|
() =>
|
|
196
205
|
props.split
|
|
197
|
-
? h('span', { class: 'visually-hidden' },
|
|
206
|
+
? h('span', { class: 'visually-hidden' }, props.splitLabel)
|
|
198
207
|
: slots.default && slots.default(),
|
|
199
208
|
)
|
|
200
209
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Ref } from 'vue'
|
|
1
2
|
import type { Placement } from '@popperjs/core'
|
|
2
3
|
import type { Placements } from '../../types'
|
|
3
4
|
import type { Alignments, Breakpoints } from './types'
|
|
@@ -49,3 +50,23 @@ export const getPlacement = (
|
|
|
49
50
|
|
|
50
51
|
return _placement
|
|
51
52
|
}
|
|
53
|
+
|
|
54
|
+
export const getReferenceElement = (
|
|
55
|
+
reference: 'parent' | 'toggle' | Ref<HTMLElement | null> | HTMLElement,
|
|
56
|
+
dropdownToggleRef: Ref<HTMLElement | null>,
|
|
57
|
+
dropdownRef: Ref<HTMLElement | null>
|
|
58
|
+
): HTMLElement | null => {
|
|
59
|
+
if (reference === 'parent') {
|
|
60
|
+
return dropdownRef.value
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (reference instanceof HTMLElement) {
|
|
64
|
+
return reference
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (reference instanceof Object && 'value' in reference) {
|
|
68
|
+
return reference.value
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return dropdownToggleRef.value
|
|
72
|
+
}
|
|
@@ -25,6 +25,28 @@ import { Color } from '../props'
|
|
|
25
25
|
const CTimePicker = defineComponent({
|
|
26
26
|
name: 'CTimePicker',
|
|
27
27
|
props: {
|
|
28
|
+
/**
|
|
29
|
+
* Accessible label for the hours selection element.
|
|
30
|
+
*/
|
|
31
|
+
ariaSelectHoursLabel: String,
|
|
32
|
+
/**
|
|
33
|
+
* Accessible label for the AM/PM selection element.
|
|
34
|
+
*
|
|
35
|
+
* @since 5.16.0
|
|
36
|
+
*/
|
|
37
|
+
ariaSelectMeridiemLabel: String,
|
|
38
|
+
/**
|
|
39
|
+
* Accessible label for the minutes selection element.
|
|
40
|
+
*
|
|
41
|
+
* @since 5.16.0
|
|
42
|
+
*/
|
|
43
|
+
ariaSelectMinutesLabel: String,
|
|
44
|
+
/**
|
|
45
|
+
* Accessible label for the seconds selection element.
|
|
46
|
+
*
|
|
47
|
+
* @since 5.16.0
|
|
48
|
+
*/
|
|
49
|
+
ariaSelectSecondsLabel: String,
|
|
28
50
|
/**
|
|
29
51
|
* Set if the component should use the 12/24 hour format. If `true` forces the interface to a 12-hour format. If `false` forces the interface into a 24-hour format. If `auto` the current locale will determine the 12 or 24-hour interface by default locales.
|
|
30
52
|
*
|
|
@@ -339,9 +361,11 @@ const CTimePicker = defineComponent({
|
|
|
339
361
|
setup(props, { emit, attrs, slots }) {
|
|
340
362
|
const formRef = ref()
|
|
341
363
|
const inputRef = ref()
|
|
342
|
-
|
|
364
|
+
const columnRefs = ref<(HTMLElement | null)[]>([])
|
|
343
365
|
const date = ref<Date | null>(convertTimeToDate(props.time))
|
|
344
|
-
const ampm = ref<'am' | 'pm'
|
|
366
|
+
const ampm = ref<'am' | 'pm' | null>(
|
|
367
|
+
date.value ? getAmPm(new Date(date.value), props.locale) : null
|
|
368
|
+
)
|
|
345
369
|
const initialDate = ref<Date | null>(null)
|
|
346
370
|
const visible = ref(props.visible)
|
|
347
371
|
const localizedTimePartials = ref<LocalizedTimePartials>({
|
|
@@ -354,6 +378,10 @@ const CTimePicker = defineComponent({
|
|
|
354
378
|
props.valid ?? (props.invalid === true ? false : undefined)
|
|
355
379
|
)
|
|
356
380
|
|
|
381
|
+
const setColumnRef = (index: number, el: HTMLElement | null) => {
|
|
382
|
+
columnRefs.value[index] = el
|
|
383
|
+
}
|
|
384
|
+
|
|
357
385
|
watch(
|
|
358
386
|
() => props.time,
|
|
359
387
|
() => {
|
|
@@ -421,22 +449,32 @@ const CTimePicker = defineComponent({
|
|
|
421
449
|
isValid.value = false
|
|
422
450
|
}
|
|
423
451
|
|
|
424
|
-
const handleTimeChange = (set: 'hours' | 'minutes' | 'seconds' | '
|
|
452
|
+
const handleTimeChange = (set: 'hours' | 'minutes' | 'seconds' | 'meridiem', value: string) => {
|
|
425
453
|
const _date = date.value || new Date('1970-01-01')
|
|
426
454
|
|
|
427
|
-
if (set === '
|
|
455
|
+
if (set === 'meridiem') {
|
|
456
|
+
const currentHours = _date.getHours()
|
|
457
|
+
|
|
428
458
|
if (value === 'am') {
|
|
429
|
-
|
|
459
|
+
ampm.value = 'am'
|
|
460
|
+
// Convert PM hours (12-23) to AM hours (0-11)
|
|
461
|
+
if (currentHours >= 12) {
|
|
462
|
+
_date.setHours(currentHours - 12)
|
|
463
|
+
}
|
|
430
464
|
}
|
|
431
465
|
|
|
432
466
|
if (value === 'pm') {
|
|
433
|
-
|
|
467
|
+
ampm.value = 'pm'
|
|
468
|
+
// Convert AM hours (0-11) to PM hours (12-23)
|
|
469
|
+
if (currentHours < 12) {
|
|
470
|
+
_date.setHours(currentHours + 12)
|
|
471
|
+
}
|
|
434
472
|
}
|
|
435
473
|
}
|
|
436
474
|
|
|
437
475
|
if (set === 'hours') {
|
|
438
476
|
if (localizedTimePartials.value && localizedTimePartials.value.hour12) {
|
|
439
|
-
_date.setHours(convert12hTo24h(ampm.value, Number.parseInt(value)))
|
|
477
|
+
_date.setHours(convert12hTo24h(ampm.value ?? 'am', Number.parseInt(value)))
|
|
440
478
|
} else {
|
|
441
479
|
_date.setHours(Number.parseInt(value))
|
|
442
480
|
}
|
|
@@ -527,6 +565,7 @@ const CTimePicker = defineComponent({
|
|
|
527
565
|
onChange: (event: Event) =>
|
|
528
566
|
handleTimeChange('hours', (event.target as HTMLSelectElement).value),
|
|
529
567
|
...(date.value && { value: getSelectedHour(date.value, props.locale) }),
|
|
568
|
+
'aria-label': props.ariaSelectHoursLabel,
|
|
530
569
|
},
|
|
531
570
|
localizedTimePartials.value &&
|
|
532
571
|
localizedTimePartials.value.listOfHours.map((option) =>
|
|
@@ -549,6 +588,7 @@ const CTimePicker = defineComponent({
|
|
|
549
588
|
onChange: (event: Event) =>
|
|
550
589
|
handleTimeChange('minutes', (event.target as HTMLSelectElement).value),
|
|
551
590
|
...(date.value && { value: getSelectedMinutes(date.value) }),
|
|
591
|
+
'aria-label': props.ariaSelectMinutesLabel,
|
|
552
592
|
},
|
|
553
593
|
localizedTimePartials.value &&
|
|
554
594
|
localizedTimePartials.value.listOfMinutes?.map((option) =>
|
|
@@ -571,6 +611,7 @@ const CTimePicker = defineComponent({
|
|
|
571
611
|
onChange: (event: Event) =>
|
|
572
612
|
handleTimeChange('seconds', (event.target as HTMLSelectElement).value),
|
|
573
613
|
...(date.value && { value: getSelectedSeconds(date.value) }),
|
|
614
|
+
'aria-label': props.ariaSelectSecondsLabel,
|
|
574
615
|
},
|
|
575
616
|
localizedTimePartials.value &&
|
|
576
617
|
localizedTimePartials.value.listOfSeconds?.map((option) =>
|
|
@@ -591,8 +632,9 @@ const CTimePicker = defineComponent({
|
|
|
591
632
|
class: 'time-picker-inline-select',
|
|
592
633
|
disabled: props.disabled,
|
|
593
634
|
onChange: (event: Event) =>
|
|
594
|
-
handleTimeChange('
|
|
635
|
+
handleTimeChange('meridiem', (event.target as HTMLSelectElement).value),
|
|
595
636
|
value: ampm.value,
|
|
637
|
+
'aria-label': props.ariaSelectMeridiemLabel,
|
|
596
638
|
},
|
|
597
639
|
[
|
|
598
640
|
h(
|
|
@@ -615,31 +657,47 @@ const CTimePicker = defineComponent({
|
|
|
615
657
|
|
|
616
658
|
const TimePickerRoll = () => [
|
|
617
659
|
h(CTimePickerRollCol, {
|
|
660
|
+
ariaLabel: props.ariaSelectHoursLabel,
|
|
661
|
+
columnIndex: 0,
|
|
662
|
+
columnRefs: columnRefs.value,
|
|
618
663
|
elements: localizedTimePartials.value && localizedTimePartials.value.listOfHours,
|
|
619
664
|
onClick: (index: number) => handleTimeChange('hours', index.toString()),
|
|
620
665
|
selected: getSelectedHour(date.value, props.locale, props.ampm),
|
|
666
|
+
setColumnRef,
|
|
621
667
|
}),
|
|
622
668
|
props.minutes &&
|
|
623
669
|
h(CTimePickerRollCol, {
|
|
670
|
+
ariaLabel: props.ariaSelectMinutesLabel,
|
|
671
|
+
columnIndex: 1,
|
|
672
|
+
columnRefs: columnRefs.value,
|
|
624
673
|
elements: localizedTimePartials.value && localizedTimePartials.value.listOfMinutes,
|
|
625
674
|
onClick: (index: number) => handleTimeChange('minutes', index.toString()),
|
|
626
675
|
selected: getSelectedMinutes(date.value),
|
|
676
|
+
setColumnRef,
|
|
627
677
|
}),
|
|
628
678
|
props.seconds &&
|
|
629
679
|
h(CTimePickerRollCol, {
|
|
680
|
+
ariaLabel: props.ariaSelectSecondsLabel,
|
|
681
|
+
columnIndex: props.minutes ? 2 : 1,
|
|
682
|
+
columnRefs: columnRefs.value,
|
|
630
683
|
elements: localizedTimePartials.value && localizedTimePartials.value.listOfSeconds,
|
|
631
684
|
onClick: (index: number) => handleTimeChange('seconds', index.toString()),
|
|
632
685
|
selected: getSelectedSeconds(date.value),
|
|
686
|
+
setColumnRef,
|
|
633
687
|
}),
|
|
634
688
|
localizedTimePartials.value &&
|
|
635
689
|
localizedTimePartials.value.hour12 &&
|
|
636
690
|
h(CTimePickerRollCol, {
|
|
691
|
+
ariaLabel: props.ariaSelectMeridiemLabel,
|
|
692
|
+
columnIndex: (props.minutes ? 1 : 0) + (props.seconds ? 1 : 0) + 1,
|
|
693
|
+
columnRefs: columnRefs.value,
|
|
637
694
|
elements: [
|
|
638
695
|
{ value: 'am', label: 'AM' },
|
|
639
696
|
{ value: 'pm', label: 'PM' },
|
|
640
697
|
],
|
|
641
|
-
onClick: (value: string) => handleTimeChange('
|
|
698
|
+
onClick: (value: string) => handleTimeChange('meridiem', value),
|
|
642
699
|
selected: ampm.value,
|
|
700
|
+
setColumnRef,
|
|
643
701
|
}),
|
|
644
702
|
]
|
|
645
703
|
|
|
@@ -711,6 +769,7 @@ const CTimePicker = defineComponent({
|
|
|
711
769
|
['time-picker-roll']: props.variant === 'roll',
|
|
712
770
|
},
|
|
713
771
|
],
|
|
772
|
+
...(props.variant !== 'select' && { role: 'group' }),
|
|
714
773
|
},
|
|
715
774
|
props.variant === 'select' ? TimePickerSelect() : TimePickerRoll()
|
|
716
775
|
),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineComponent, h, onUpdated, PropType, ref, watch } from 'vue'
|
|
2
2
|
|
|
3
3
|
import { useIsVisible } from '../../composables'
|
|
4
|
+
import { getNextActiveElement, isRTL } from '../../utils'
|
|
4
5
|
|
|
5
6
|
export interface Element {
|
|
6
7
|
value: number | string
|
|
@@ -10,13 +11,18 @@ export interface Element {
|
|
|
10
11
|
const CTimePickerRollCol = defineComponent({
|
|
11
12
|
name: 'CTimePickerRollCol',
|
|
12
13
|
props: {
|
|
14
|
+
ariaLabel: String,
|
|
15
|
+
columnIndex: Number,
|
|
16
|
+
columnRefs: {
|
|
17
|
+
type: Array as PropType<(HTMLElement | null)[]>,
|
|
18
|
+
default: () => [],
|
|
19
|
+
},
|
|
13
20
|
elements: {
|
|
14
21
|
type: Array as PropType<Element[]>,
|
|
15
22
|
required: true,
|
|
16
23
|
},
|
|
17
|
-
selected:
|
|
18
|
-
|
|
19
|
-
},
|
|
24
|
+
selected: [Number, String, null],
|
|
25
|
+
setColumnRef: Function as PropType<(index: number, el: HTMLElement | null) => void>,
|
|
20
26
|
},
|
|
21
27
|
emits: ['click'],
|
|
22
28
|
setup(props, { emit }) {
|
|
@@ -29,7 +35,7 @@ const CTimePickerRollCol = defineComponent({
|
|
|
29
35
|
if (isVisible.value && nodeEl && nodeEl instanceof HTMLElement) {
|
|
30
36
|
colRef.value?.scrollTo({
|
|
31
37
|
top: nodeEl.offsetTop,
|
|
32
|
-
behavior: init.value ? '
|
|
38
|
+
behavior: init.value ? 'instant' : 'smooth',
|
|
33
39
|
})
|
|
34
40
|
}
|
|
35
41
|
}
|
|
@@ -42,35 +48,107 @@ const CTimePickerRollCol = defineComponent({
|
|
|
42
48
|
}
|
|
43
49
|
})
|
|
44
50
|
|
|
51
|
+
watch(colRef, () => {
|
|
52
|
+
if (props.columnIndex !== undefined && props.setColumnRef) {
|
|
53
|
+
props.setColumnRef(props.columnIndex, colRef.value || null)
|
|
54
|
+
}
|
|
55
|
+
}, { immediate: true })
|
|
56
|
+
|
|
45
57
|
onUpdated(() => {
|
|
46
58
|
scrollToSelectedElement()
|
|
47
59
|
})
|
|
48
60
|
|
|
61
|
+
const moveFocusToNextColumn = () => {
|
|
62
|
+
if (!props.columnRefs || props.columnIndex === undefined) return
|
|
63
|
+
|
|
64
|
+
if (props.columnIndex < props.columnRefs.length - 1) {
|
|
65
|
+
const column = props.columnRefs[props.columnIndex + 1]
|
|
66
|
+
const focusableCell = column?.querySelector('.time-picker-roll-cell[tabindex="0"]') as HTMLElement
|
|
67
|
+
focusableCell?.focus()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const moveFocusToPreviousColumn = () => {
|
|
72
|
+
if (!props.columnRefs || props.columnIndex === undefined) return
|
|
73
|
+
|
|
74
|
+
if (props.columnIndex > 0) {
|
|
75
|
+
const column = props.columnRefs[props.columnIndex - 1]
|
|
76
|
+
const focusableCell = column?.querySelector('.time-picker-roll-cell[tabindex="0"]') as HTMLElement
|
|
77
|
+
focusableCell?.focus()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
49
81
|
const handleKeyDown = (event: KeyboardEvent, value: number | string) => {
|
|
50
82
|
if (event.code === 'Space' || event.key === 'Enter') {
|
|
51
83
|
event.preventDefault()
|
|
52
84
|
emit('click', value)
|
|
85
|
+
moveFocusToNextColumn()
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (colRef.value && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
|
|
90
|
+
event.preventDefault()
|
|
91
|
+
const target = event.target as HTMLElement
|
|
92
|
+
const items = [...colRef.value.querySelectorAll('.time-picker-roll-cell')] as HTMLElement[]
|
|
93
|
+
|
|
94
|
+
getNextActiveElement(items, target, event.key === 'ArrowDown', true).focus()
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (colRef.value && (event.key === 'Home' || event.key === 'End')) {
|
|
99
|
+
event.preventDefault()
|
|
100
|
+
const items = [...colRef.value.querySelectorAll('.time-picker-roll-cell')] as HTMLElement[]
|
|
101
|
+
const index = event.key === 'Home' ? 0 : items.length - 1
|
|
102
|
+
items[index]?.focus()
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (colRef.value && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
|
|
107
|
+
event.preventDefault()
|
|
108
|
+
const rtl = isRTL(colRef.value)
|
|
109
|
+
const shouldGoLeft =
|
|
110
|
+
(event.key === 'ArrowLeft' && !rtl) || (event.key === 'ArrowRight' && rtl)
|
|
111
|
+
if (shouldGoLeft) {
|
|
112
|
+
moveFocusToPreviousColumn()
|
|
113
|
+
} else {
|
|
114
|
+
moveFocusToNextColumn()
|
|
115
|
+
}
|
|
53
116
|
}
|
|
54
117
|
}
|
|
55
118
|
|
|
56
119
|
return () =>
|
|
57
120
|
h(
|
|
58
121
|
'div',
|
|
59
|
-
{
|
|
60
|
-
|
|
122
|
+
{
|
|
123
|
+
class: 'time-picker-roll-col',
|
|
124
|
+
onFocusout: (event: FocusEvent) => {
|
|
125
|
+
const currentTarget = event.currentTarget as HTMLElement
|
|
126
|
+
const relatedTarget = event.relatedTarget as HTMLElement | null
|
|
127
|
+
if (currentTarget && (!relatedTarget || !currentTarget.contains(relatedTarget))) {
|
|
128
|
+
scrollToSelectedElement()
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
ref: colRef,
|
|
132
|
+
role: 'listbox',
|
|
133
|
+
'aria-label': props.ariaLabel,
|
|
134
|
+
},
|
|
135
|
+
props.elements.map((element, index) => {
|
|
136
|
+
const isSelected = element.value === props.selected
|
|
61
137
|
return h(
|
|
62
138
|
'div',
|
|
63
139
|
{
|
|
64
140
|
class: [
|
|
65
141
|
'time-picker-roll-cell',
|
|
66
142
|
{
|
|
67
|
-
selected:
|
|
143
|
+
selected: isSelected,
|
|
68
144
|
},
|
|
69
145
|
],
|
|
70
146
|
onClick: () => emit('click', element.value),
|
|
71
147
|
onKeydown: (event: KeyboardEvent) => handleKeyDown(event, element.value),
|
|
72
|
-
role: '
|
|
73
|
-
tabindex: 0,
|
|
148
|
+
role: 'option',
|
|
149
|
+
tabindex: isSelected ? 0 : props.selected ? -1 : index === 0 ? 0 : -1,
|
|
150
|
+
'aria-label': element.label.toString(),
|
|
151
|
+
'aria-selected': isSelected,
|
|
74
152
|
},
|
|
75
153
|
element.label
|
|
76
154
|
)
|