@coreui/vue-pro 5.14.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.
Files changed (127) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/components/calendar/CCalendar.js +67 -65
  3. package/dist/cjs/components/calendar/CCalendar.js.map +1 -1
  4. package/dist/cjs/components/calendar/utils.d.ts +53 -2
  5. package/dist/cjs/components/calendar/utils.js +466 -43
  6. package/dist/cjs/components/calendar/utils.js.map +1 -1
  7. package/dist/cjs/components/date-range-picker/CDateRangePicker.js +86 -57
  8. package/dist/cjs/components/date-range-picker/CDateRangePicker.js.map +1 -1
  9. package/dist/cjs/components/date-range-picker/utils.d.ts +0 -9
  10. package/dist/cjs/components/date-range-picker/utils.js +0 -38
  11. package/dist/cjs/components/date-range-picker/utils.js.map +1 -1
  12. package/dist/cjs/components/dropdown/CDropdown.d.ts +32 -7
  13. package/dist/cjs/components/dropdown/CDropdown.js +67 -29
  14. package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
  15. package/dist/cjs/components/dropdown/CDropdownToggle.d.ts +19 -0
  16. package/dist/cjs/components/dropdown/CDropdownToggle.js +17 -2
  17. package/dist/cjs/components/dropdown/CDropdownToggle.js.map +1 -1
  18. package/dist/cjs/components/dropdown/utils.d.ts +2 -0
  19. package/dist/cjs/components/dropdown/utils.js +13 -0
  20. package/dist/cjs/components/dropdown/utils.js.map +1 -1
  21. package/dist/cjs/components/focus-trap/CFocusTrap.d.ts +108 -0
  22. package/dist/cjs/components/focus-trap/CFocusTrap.js +254 -0
  23. package/dist/cjs/components/focus-trap/CFocusTrap.js.map +1 -0
  24. package/dist/cjs/components/focus-trap/index.d.ts +6 -0
  25. package/dist/cjs/components/focus-trap/index.js +13 -0
  26. package/dist/cjs/components/focus-trap/index.js.map +1 -0
  27. package/dist/cjs/components/focus-trap/utils.d.ts +28 -0
  28. package/dist/cjs/components/focus-trap/utils.js +83 -0
  29. package/dist/cjs/components/focus-trap/utils.js.map +1 -0
  30. package/dist/cjs/components/index.d.ts +1 -0
  31. package/dist/cjs/components/index.js +70 -66
  32. package/dist/cjs/components/index.js.map +1 -1
  33. package/dist/cjs/components/modal/CModal.d.ts +2 -2
  34. package/dist/cjs/components/modal/CModal.js +19 -27
  35. package/dist/cjs/components/modal/CModal.js.map +1 -1
  36. package/dist/cjs/components/modal/CModalHeader.js +4 -2
  37. package/dist/cjs/components/modal/CModalHeader.js.map +1 -1
  38. package/dist/cjs/components/nav/CNavItem.d.ts +2 -2
  39. package/dist/cjs/components/offcanvas/COffcanvas.js +3 -2
  40. package/dist/cjs/components/offcanvas/COffcanvas.js.map +1 -1
  41. package/dist/cjs/components/picker/CPicker.js +3 -2
  42. package/dist/cjs/components/picker/CPicker.js.map +1 -1
  43. package/dist/cjs/components/time-picker/CTimePicker.d.ts +45 -1
  44. package/dist/cjs/components/time-picker/CTimePicker.js +64 -8
  45. package/dist/cjs/components/time-picker/CTimePicker.js.map +1 -1
  46. package/dist/cjs/components/time-picker/CTimePickerRollCol.d.ts +19 -7
  47. package/dist/cjs/components/time-picker/CTimePickerRollCol.js +80 -8
  48. package/dist/cjs/components/time-picker/CTimePickerRollCol.js.map +1 -1
  49. package/dist/cjs/components/time-picker/utils.d.ts +1 -1
  50. package/dist/cjs/composables/useDebouncedCallback.d.ts +1 -1
  51. package/dist/cjs/composables/useDebouncedCallback.js +1 -1
  52. package/dist/cjs/composables/useDebouncedCallback.js.map +1 -1
  53. package/dist/cjs/index.js +76 -72
  54. package/dist/cjs/index.js.map +1 -1
  55. package/dist/esm/components/calendar/CCalendar.js +67 -65
  56. package/dist/esm/components/calendar/CCalendar.js.map +1 -1
  57. package/dist/esm/components/calendar/utils.d.ts +53 -2
  58. package/dist/esm/components/calendar/utils.js +464 -44
  59. package/dist/esm/components/calendar/utils.js.map +1 -1
  60. package/dist/esm/components/date-range-picker/CDateRangePicker.js +86 -57
  61. package/dist/esm/components/date-range-picker/CDateRangePicker.js.map +1 -1
  62. package/dist/esm/components/date-range-picker/utils.d.ts +0 -9
  63. package/dist/esm/components/date-range-picker/utils.js +1 -38
  64. package/dist/esm/components/date-range-picker/utils.js.map +1 -1
  65. package/dist/esm/components/dropdown/CDropdown.d.ts +32 -7
  66. package/dist/esm/components/dropdown/CDropdown.js +69 -31
  67. package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
  68. package/dist/esm/components/dropdown/CDropdownToggle.d.ts +19 -0
  69. package/dist/esm/components/dropdown/CDropdownToggle.js +17 -2
  70. package/dist/esm/components/dropdown/CDropdownToggle.js.map +1 -1
  71. package/dist/esm/components/dropdown/utils.d.ts +2 -0
  72. package/dist/esm/components/dropdown/utils.js +13 -1
  73. package/dist/esm/components/dropdown/utils.js.map +1 -1
  74. package/dist/esm/components/focus-trap/CFocusTrap.d.ts +108 -0
  75. package/dist/esm/components/focus-trap/CFocusTrap.js +252 -0
  76. package/dist/esm/components/focus-trap/CFocusTrap.js.map +1 -0
  77. package/dist/esm/components/focus-trap/index.d.ts +6 -0
  78. package/dist/esm/components/focus-trap/index.js +10 -0
  79. package/dist/esm/components/focus-trap/index.js.map +1 -0
  80. package/dist/esm/components/focus-trap/utils.d.ts +28 -0
  81. package/dist/esm/components/focus-trap/utils.js +78 -0
  82. package/dist/esm/components/focus-trap/utils.js.map +1 -0
  83. package/dist/esm/components/index.d.ts +1 -0
  84. package/dist/esm/components/index.js +2 -0
  85. package/dist/esm/components/index.js.map +1 -1
  86. package/dist/esm/components/modal/CModal.d.ts +2 -2
  87. package/dist/esm/components/modal/CModal.js +19 -27
  88. package/dist/esm/components/modal/CModal.js.map +1 -1
  89. package/dist/esm/components/modal/CModalHeader.js +4 -2
  90. package/dist/esm/components/modal/CModalHeader.js.map +1 -1
  91. package/dist/esm/components/nav/CNavItem.d.ts +2 -2
  92. package/dist/esm/components/offcanvas/COffcanvas.js +3 -2
  93. package/dist/esm/components/offcanvas/COffcanvas.js.map +1 -1
  94. package/dist/esm/components/picker/CPicker.js +3 -2
  95. package/dist/esm/components/picker/CPicker.js.map +1 -1
  96. package/dist/esm/components/time-picker/CTimePicker.d.ts +45 -1
  97. package/dist/esm/components/time-picker/CTimePicker.js +64 -8
  98. package/dist/esm/components/time-picker/CTimePicker.js.map +1 -1
  99. package/dist/esm/components/time-picker/CTimePickerRollCol.d.ts +19 -7
  100. package/dist/esm/components/time-picker/CTimePickerRollCol.js +80 -8
  101. package/dist/esm/components/time-picker/CTimePickerRollCol.js.map +1 -1
  102. package/dist/esm/components/time-picker/utils.d.ts +1 -1
  103. package/dist/esm/composables/useDebouncedCallback.d.ts +1 -1
  104. package/dist/esm/composables/useDebouncedCallback.js +1 -1
  105. package/dist/esm/composables/useDebouncedCallback.js.map +1 -1
  106. package/dist/esm/index.js +2 -0
  107. package/dist/esm/index.js.map +1 -1
  108. package/package.json +5 -5
  109. package/src/components/calendar/CCalendar.ts +61 -70
  110. package/src/components/calendar/utils.ts +595 -47
  111. package/src/components/date-range-picker/CDateRangePicker.ts +131 -82
  112. package/src/components/date-range-picker/utils.ts +0 -58
  113. package/src/components/dropdown/CDropdown.ts +119 -52
  114. package/src/components/dropdown/CDropdownToggle.ts +18 -3
  115. package/src/components/dropdown/utils.ts +21 -0
  116. package/src/components/focus-trap/CFocusTrap.ts +303 -0
  117. package/src/components/focus-trap/index.ts +10 -0
  118. package/src/components/focus-trap/utils.ts +90 -0
  119. package/src/components/index.ts +1 -0
  120. package/src/components/modal/CModal.ts +32 -37
  121. package/src/components/modal/CModalHeader.ts +5 -3
  122. package/src/components/nav/CNavItem.ts +1 -1
  123. package/src/components/offcanvas/COffcanvas.ts +40 -36
  124. package/src/components/picker/CPicker.ts +58 -52
  125. package/src/components/time-picker/CTimePicker.ts +80 -22
  126. package/src/components/time-picker/CTimePickerRollCol.ts +87 -9
  127. package/src/composables/useDebouncedCallback.ts +1 -1
@@ -1,6 +1,7 @@
1
1
  import { defineComponent, h, ref, RendererElement, Transition, watch, withDirectives } from 'vue'
2
2
 
3
3
  import { CBackdrop } from '../backdrop'
4
+ import { CFocusTrap } from '../focus-trap'
4
5
 
5
6
  import { vVisible } from '../../directives/v-c-visible'
6
7
  import { executeAfterTransition } from '../../utils/transition'
@@ -103,7 +104,7 @@ const COffcanvas = defineComponent({
103
104
  () => props.visible,
104
105
  () => {
105
106
  visible.value = props.visible
106
- },
107
+ }
107
108
  )
108
109
 
109
110
  watch(visible, () => {
@@ -161,41 +162,44 @@ const COffcanvas = defineComponent({
161
162
  }
162
163
 
163
164
  return () => [
164
- h(
165
- Transition,
166
- {
167
- appear: visible.value,
168
- css: false,
169
- onEnter: (el, done) => handleEnter(el, done),
170
- onAfterEnter: () => handleAfterEnter(),
171
- onLeave: (el, done) => handleLeave(el, done),
172
- onAfterLeave: (el) => handleAfterLeave(el),
173
- },
174
- () =>
175
- withDirectives(
176
- h(
177
- 'div',
178
- {
179
- ...attrs,
180
- class: [
181
- {
182
- [`offcanvas${
183
- typeof props.responsive === 'boolean' ? '' : '-' + props.responsive
184
- }`]: props.responsive,
185
- [`offcanvas-${props.placement}`]: props.placement,
186
- },
187
- attrs.class,
188
- ],
189
- onKeydown: (event: KeyboardEvent) => handleKeyDown(event),
190
- ref: offcanvasRef,
191
- role: 'dialog',
192
- tabindex: -1,
193
- ...(props.dark && { 'data-coreui-theme': 'dark' }),
194
- },
195
- slots.default && slots.default(),
196
- ),
197
- [[vVisible, props.visible]],
198
- ),
165
+ h(CFocusTrap, { active: visible.value && Boolean(props.backdrop) }, () =>
166
+ h(
167
+ Transition,
168
+ {
169
+ appear: visible.value,
170
+ css: false,
171
+ onEnter: (el, done) => handleEnter(el, done),
172
+ onAfterEnter: () => handleAfterEnter(),
173
+ onLeave: (el, done) => handleLeave(el, done),
174
+ onAfterLeave: (el) => handleAfterLeave(el),
175
+ },
176
+ () =>
177
+ withDirectives(
178
+ h(
179
+ 'div',
180
+ {
181
+ ...attrs,
182
+ class: [
183
+ {
184
+ [`offcanvas${
185
+ typeof props.responsive === 'boolean' ? '' : '-' + props.responsive
186
+ }`]: props.responsive,
187
+ [`offcanvas-${props.placement}`]: props.placement,
188
+ },
189
+ attrs.class,
190
+ ],
191
+ onKeydown: (event: KeyboardEvent) => handleKeyDown(event),
192
+ ref: offcanvasRef,
193
+ role: 'dialog',
194
+ tabindex: -1,
195
+ ...(props.dark && { 'data-coreui-theme': 'dark' }),
196
+ },
197
+ slots.default && slots.default()
198
+ ),
199
+
200
+ [[vVisible, props.visible]]
201
+ )
202
+ )
199
203
  ),
200
204
  props.backdrop &&
201
205
  h(CBackdrop, {
@@ -2,6 +2,7 @@ import { cloneVNode, defineComponent, h, PropType, ref, watch } from 'vue'
2
2
  import { createPopper } from '@popperjs/core'
3
3
 
4
4
  import { CConditionalTeleport } from '../conditional-teleport'
5
+ import { CFocusTrap } from '../focus-trap'
5
6
 
6
7
  import { isRTL } from '../../utils'
7
8
 
@@ -63,7 +64,7 @@ const CPicker = defineComponent({
63
64
  () => props.visible,
64
65
  () => {
65
66
  visible.value = props.visible
66
- },
67
+ }
67
68
  )
68
69
 
69
70
  watch(visible, () => {
@@ -142,62 +143,67 @@ const CPicker = defineComponent({
142
143
  default: {
143
144
  return () =>
144
145
  h(
145
- 'div',
146
- {
147
- class: [
148
- attrs.class,
149
- {
150
- show: visible.value,
151
- },
152
- ],
153
- ref: pickerRef,
154
- },
155
- [
156
- /**
157
- * @slot Location for the toggler element.
158
- */
159
- slots.toggler &&
160
- slots.toggler().map((slot) =>
161
- cloneVNode(slot, {
162
- onClick: () => {
163
- if (!props.disabled && !visible.value) {
164
- visible.value = true
165
- }
166
- },
167
- ref: (el) => {
168
- togglerRef.value = el
169
- },
170
- }),
171
- ),
146
+ CFocusTrap,
147
+ { active: visible.value, ...(props.teleport && { additionalContainer: dropdownRef }) },
148
+ () =>
172
149
  h(
173
- CConditionalTeleport,
150
+ 'div',
174
151
  {
175
- teleport: props.teleport,
152
+ class: [
153
+ attrs.class,
154
+ {
155
+ show: visible.value,
156
+ },
157
+ ],
158
+ ref: pickerRef,
176
159
  },
177
- {
178
- default: () =>
179
- h(
180
- 'div',
181
- {
182
- class: [
183
- props.dropdownClassNames,
160
+ [
161
+ /**
162
+ * @slot Location for the toggler element.
163
+ */
164
+ slots.toggler &&
165
+ slots.toggler().map((slot) =>
166
+ cloneVNode(slot, {
167
+ onClick: () => {
168
+ if (!props.disabled && !visible.value) {
169
+ visible.value = true
170
+ }
171
+ },
172
+ ref: (el) => {
173
+ togglerRef.value = el
174
+ },
175
+ })
176
+ ),
177
+ h(
178
+ CConditionalTeleport,
179
+ {
180
+ teleport: props.teleport,
181
+ },
182
+ {
183
+ default: () =>
184
+ h(
185
+ 'div',
184
186
  {
185
- show: props.teleport && visible.value,
187
+ class: [
188
+ props.dropdownClassNames,
189
+ {
190
+ show: props.teleport && visible.value,
191
+ },
192
+ ],
193
+ ref: dropdownRef,
186
194
  },
187
- ],
188
- ref: dropdownRef,
189
- },
190
- [
191
- slots.default && slots.default(),
192
- /**
193
- * @slot Location for the footer element.
194
- */
195
- props.footer && slots.footer && slots.footer(),
196
- ],
197
- ),
198
- },
199
- ),
200
- ],
195
+ [
196
+ slots.default && slots.default(),
197
+ /**
198
+ * @slot Location for the footer element.
199
+ */
200
+ props.footer && slots.footer && slots.footer(),
201
+ ]
202
+ ),
203
+ }
204
+ ),
205
+ ]
206
+ )
201
207
  )
202
208
  }
203
209
  }
@@ -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'>(date.value ? getAmPm(new Date(date.value), props.locale) : 'am')
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' | 'toggle', value: string) => {
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 === 'toggle') {
455
+ if (set === 'meridiem') {
456
+ const currentHours = _date.getHours()
457
+
428
458
  if (value === 'am') {
429
- _date.setHours(_date.getHours() - 12)
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
- _date.setHours(_date.getHours() + 12)
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
  }
@@ -463,24 +501,23 @@ const CTimePicker = defineComponent({
463
501
  disabled: props.disabled,
464
502
  id: props.id,
465
503
  name: props.name,
466
- onInput: (event: Event) =>
467
- useDebouncedCallback(() => {
468
- if (isValidTime((event.target as HTMLInputElement).value)) {
469
- const _date = convertTimeToDate((event.target as HTMLInputElement).value)
470
- date.value = _date
504
+ onInput: useDebouncedCallback((event: Event) => {
505
+ if (isValidTime((event.target as HTMLInputElement).value)) {
506
+ const _date = convertTimeToDate((event.target as HTMLInputElement).value)
507
+ date.value = _date
471
508
 
472
- if (_date) {
473
- emit('change', _date.toTimeString(), _date.toLocaleTimeString(props.locale), _date)
474
- emit('update:time', _date.toLocaleTimeString(props.locale))
475
- } else {
476
- emit('change', null)
477
- emit('update:time', null)
478
- }
509
+ if (_date) {
510
+ emit('change', _date.toTimeString(), _date.toLocaleTimeString(props.locale), _date)
511
+ emit('update:time', _date.toLocaleTimeString(props.locale))
479
512
  } else {
480
513
  emit('change', null)
481
514
  emit('update:time', null)
482
515
  }
483
- }, props.inputOnChangeDelay),
516
+ } else {
517
+ emit('change', null)
518
+ emit('update:time', null)
519
+ }
520
+ }, props.inputOnChangeDelay),
484
521
  placeholder: props.placeholder,
485
522
  readonly: props.inputReadOnly,
486
523
  ref: inputRef,
@@ -528,6 +565,7 @@ const CTimePicker = defineComponent({
528
565
  onChange: (event: Event) =>
529
566
  handleTimeChange('hours', (event.target as HTMLSelectElement).value),
530
567
  ...(date.value && { value: getSelectedHour(date.value, props.locale) }),
568
+ 'aria-label': props.ariaSelectHoursLabel,
531
569
  },
532
570
  localizedTimePartials.value &&
533
571
  localizedTimePartials.value.listOfHours.map((option) =>
@@ -550,6 +588,7 @@ const CTimePicker = defineComponent({
550
588
  onChange: (event: Event) =>
551
589
  handleTimeChange('minutes', (event.target as HTMLSelectElement).value),
552
590
  ...(date.value && { value: getSelectedMinutes(date.value) }),
591
+ 'aria-label': props.ariaSelectMinutesLabel,
553
592
  },
554
593
  localizedTimePartials.value &&
555
594
  localizedTimePartials.value.listOfMinutes?.map((option) =>
@@ -572,6 +611,7 @@ const CTimePicker = defineComponent({
572
611
  onChange: (event: Event) =>
573
612
  handleTimeChange('seconds', (event.target as HTMLSelectElement).value),
574
613
  ...(date.value && { value: getSelectedSeconds(date.value) }),
614
+ 'aria-label': props.ariaSelectSecondsLabel,
575
615
  },
576
616
  localizedTimePartials.value &&
577
617
  localizedTimePartials.value.listOfSeconds?.map((option) =>
@@ -592,8 +632,9 @@ const CTimePicker = defineComponent({
592
632
  class: 'time-picker-inline-select',
593
633
  disabled: props.disabled,
594
634
  onChange: (event: Event) =>
595
- handleTimeChange('toggle', (event.target as HTMLSelectElement).value),
635
+ handleTimeChange('meridiem', (event.target as HTMLSelectElement).value),
596
636
  value: ampm.value,
637
+ 'aria-label': props.ariaSelectMeridiemLabel,
597
638
  },
598
639
  [
599
640
  h(
@@ -616,31 +657,47 @@ const CTimePicker = defineComponent({
616
657
 
617
658
  const TimePickerRoll = () => [
618
659
  h(CTimePickerRollCol, {
660
+ ariaLabel: props.ariaSelectHoursLabel,
661
+ columnIndex: 0,
662
+ columnRefs: columnRefs.value,
619
663
  elements: localizedTimePartials.value && localizedTimePartials.value.listOfHours,
620
664
  onClick: (index: number) => handleTimeChange('hours', index.toString()),
621
665
  selected: getSelectedHour(date.value, props.locale, props.ampm),
666
+ setColumnRef,
622
667
  }),
623
668
  props.minutes &&
624
669
  h(CTimePickerRollCol, {
670
+ ariaLabel: props.ariaSelectMinutesLabel,
671
+ columnIndex: 1,
672
+ columnRefs: columnRefs.value,
625
673
  elements: localizedTimePartials.value && localizedTimePartials.value.listOfMinutes,
626
674
  onClick: (index: number) => handleTimeChange('minutes', index.toString()),
627
675
  selected: getSelectedMinutes(date.value),
676
+ setColumnRef,
628
677
  }),
629
678
  props.seconds &&
630
679
  h(CTimePickerRollCol, {
680
+ ariaLabel: props.ariaSelectSecondsLabel,
681
+ columnIndex: props.minutes ? 2 : 1,
682
+ columnRefs: columnRefs.value,
631
683
  elements: localizedTimePartials.value && localizedTimePartials.value.listOfSeconds,
632
684
  onClick: (index: number) => handleTimeChange('seconds', index.toString()),
633
685
  selected: getSelectedSeconds(date.value),
686
+ setColumnRef,
634
687
  }),
635
688
  localizedTimePartials.value &&
636
689
  localizedTimePartials.value.hour12 &&
637
690
  h(CTimePickerRollCol, {
691
+ ariaLabel: props.ariaSelectMeridiemLabel,
692
+ columnIndex: (props.minutes ? 1 : 0) + (props.seconds ? 1 : 0) + 1,
693
+ columnRefs: columnRefs.value,
638
694
  elements: [
639
695
  { value: 'am', label: 'AM' },
640
696
  { value: 'pm', label: 'PM' },
641
697
  ],
642
- onClick: (value: string) => handleTimeChange('toggle', value),
698
+ onClick: (value: string) => handleTimeChange('meridiem', value),
643
699
  selected: ampm.value,
700
+ setColumnRef,
644
701
  }),
645
702
  ]
646
703
 
@@ -712,6 +769,7 @@ const CTimePicker = defineComponent({
712
769
  ['time-picker-roll']: props.variant === 'roll',
713
770
  },
714
771
  ],
772
+ ...(props.variant !== 'select' && { role: 'group' }),
715
773
  },
716
774
  props.variant === 'select' ? TimePickerSelect() : TimePickerRoll()
717
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
- type: [Number, String],
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 ? 'auto' : 'smooth',
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
- { class: 'time-picker-roll-col', ref: colRef },
60
- props.elements.map((element) => {
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: element.value === props.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: 'button',
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
  )
@@ -12,5 +12,5 @@ export const useDebouncedCallback = <F extends Function>(callback: F, delay: num
12
12
  timeout.value = setTimeout(handler, delay)
13
13
  }
14
14
 
15
- return debouncedFn()
15
+ return debouncedFn
16
16
  }