@@ -16,6 +16,8 @@ import {
1616 oneMonthAhead ,
1717 oneYearAgo ,
1818 oneYearAhead ,
19+ oneDecadeAgo ,
20+ oneDecadeAhead ,
1921 parseYMD ,
2022 resolveLocale
2123} from '../../utils/date'
@@ -26,7 +28,12 @@ import { toInteger } from '../../utils/number'
2628import { toString } from '../../utils/string'
2729import idMixin from '../../mixins/id'
2830import normalizeSlotMixin from '../../mixins/normalize-slot'
29- import { BIconChevronLeft , BIconChevronDoubleLeft , BIconCircleFill } from '../../icons/icons'
31+ import {
32+ BIconChevronLeft ,
33+ BIconChevronDoubleLeft ,
34+ BIconChevronBarLeft ,
35+ BIconCircleFill
36+ } from '../../icons/icons'
3037
3138// --- Constants ---
3239
@@ -141,6 +148,11 @@ export const BCalendar = Vue.extend({
141148 type : Boolean ,
142149 default : false
143150 } ,
151+ showDecadeNav : {
152+ // When `true` enables the decade navigation buttons
153+ type : Boolean ,
154+ default : false
155+ } ,
144156 hidden : {
145157 // When `true`, renders a comment node, but keeps the component instance active
146158 // Mainly for <b-form-date>, so that we can get the component's value and locale
@@ -158,6 +170,10 @@ export const BCalendar = Vue.extend({
158170 // default: null
159171 } ,
160172 // Labels for buttons and keyboard shortcuts
173+ labelPrevDecade : {
174+ type : String ,
175+ default : ( ) => getComponentConfig ( NAME , 'labelPrevDecade' )
176+ } ,
161177 labelPrevYear : {
162178 type : String ,
163179 default : ( ) => getComponentConfig ( NAME , 'labelPrevYear' )
@@ -178,6 +194,10 @@ export const BCalendar = Vue.extend({
178194 type : String ,
179195 default : ( ) => getComponentConfig ( NAME , 'labelNextYear' )
180196 } ,
197+ labelNextDecade : {
198+ type : String ,
199+ default : ( ) => getComponentConfig ( NAME , 'labelNextDecade' )
200+ } ,
181201 labelToday : {
182202 type : String ,
183203 default : ( ) => getComponentConfig ( NAME , 'labelToday' )
@@ -397,6 +417,10 @@ export const BCalendar = Vue.extend({
397417 return createDateFormatter ( this . calendarLocale , { day : 'numeric' , calendar : 'gregory' } )
398418 } ,
399419 // Disabled states for the nav buttons
420+ prevDecadeDisabled ( ) {
421+ const min = this . computedMin
422+ return this . disabled || ( min && lastDateOfMonth ( oneDecadeAgo ( this . activeDate ) ) < min )
423+ } ,
400424 prevYearDisabled ( ) {
401425 const min = this . computedMin
402426 return this . disabled || ( min && lastDateOfMonth ( oneYearAgo ( this . activeDate ) ) < min )
@@ -417,7 +441,11 @@ export const BCalendar = Vue.extend({
417441 const max = this . computedMax
418442 return this . disabled || ( max && firstDateOfMonth ( oneYearAhead ( this . activeDate ) ) > max )
419443 } ,
420- // Calendar generation
444+ nextDecadeDisabled ( ) {
445+ const max = this . computedMax
446+ return this . disabled || ( max && firstDateOfMonth ( oneDecadeAhead ( this . activeDate ) ) > max )
447+ } ,
448+ // Calendar dates generation
421449 calendar ( ) {
422450 const matrix = [ ]
423451 const firstDay = this . calendarFirstDay
@@ -571,8 +599,7 @@ export const BCalendar = Vue.extend({
571599 // Calendar keyboard navigation
572600 // Handles PAGEUP/PAGEDOWN/END/HOME/LEFT/UP/RIGHT/DOWN
573601 // Focuses grid after updating
574- const keyCode = evt . keyCode
575- const altKey = evt . altKey
602+ const { altKey, ctrlKey, keyCode } = evt
576603 if ( ! arrayIncludes ( [ PAGEUP , PAGEDOWN , END , HOME , LEFT , UP , RIGHT , DOWN ] , keyCode ) ) {
577604 /* istanbul ignore next */
578605 return
@@ -586,13 +613,15 @@ export const BCalendar = Vue.extend({
586613 const isRTL = this . isRTL
587614 if ( keyCode === PAGEUP ) {
588615 // PAGEUP - Previous month/year
589- activeDate = ( altKey ? oneYearAgo : oneMonthAgo ) ( activeDate )
616+ activeDate = ( altKey ? ( ctrlKey ? oneDecadeAgo : oneYearAgo ) : oneMonthAgo ) ( activeDate )
590617 // We check the first day of month to be in rage
591618 checkDate = createDate ( activeDate )
592619 checkDate . setDate ( 1 )
593620 } else if ( keyCode === PAGEDOWN ) {
594621 // PAGEDOWN - Next month/year
595- activeDate = ( altKey ? oneYearAhead : oneMonthAhead ) ( activeDate )
622+ activeDate = ( altKey ? ( ctrlKey ? oneDecadeAhead : oneYearAhead ) : oneMonthAhead ) (
623+ activeDate
624+ )
596625 // We check the last day of month to be in rage
597626 checkDate = createDate ( activeDate )
598627 checkDate . setMonth ( checkDate . getMonth ( ) + 1 )
@@ -670,6 +699,9 @@ export const BCalendar = Vue.extend({
670699 this . focus ( )
671700 }
672701 } ,
702+ gotoPrevDecade ( ) {
703+ this . activeYMD = formatYMD ( this . constrainDate ( oneDecadeAgo ( this . activeDate ) ) )
704+ } ,
673705 gotoPrevYear ( ) {
674706 this . activeYMD = formatYMD ( this . constrainDate ( oneYearAgo ( this . activeDate ) ) )
675707 } ,
@@ -686,6 +718,9 @@ export const BCalendar = Vue.extend({
686718 gotoNextYear ( ) {
687719 this . activeYMD = formatYMD ( this . constrainDate ( oneYearAhead ( this . activeDate ) ) )
688720 } ,
721+ gotoNextDecade ( ) {
722+ this . activeYMD = formatYMD ( this . constrainDate ( oneDecadeAhead ( this . activeDate ) ) )
723+ } ,
689724 onHeaderClick ( ) {
690725 if ( ! this . disabled ) {
691726 this . activeYMD = this . selectedYMD || formatYMD ( this . getToday ( ) )
@@ -700,6 +735,7 @@ export const BCalendar = Vue.extend({
700735 }
701736
702737 const isRTL = this . isRTL
738+ const hideDecadeNav = ! this . showDecadeNav
703739 const todayYMD = formatYMD ( this . getToday ( ) )
704740 const selectedYMD = this . selectedYMD
705741 const activeYMD = this . activeYMD
@@ -762,11 +798,13 @@ export const BCalendar = Vue.extend({
762798 )
763799
764800 // Content for the date navigation buttons
801+ const $prevDecadeIcon = h ( BIconChevronBarLeft , { props : { shiftV : 0.5 , flipH : isRTL } } )
765802 const $prevYearIcon = h ( BIconChevronDoubleLeft , { props : { shiftV : 0.5 , flipH : isRTL } } )
766803 const $prevMonthIcon = h ( BIconChevronLeft , { props : { shiftV : 0.5 , flipH : isRTL } } )
767804 const $thisMonthIcon = h ( BIconCircleFill , { props : { shiftV : 0.5 } } )
768805 const $nextMonthIcon = h ( BIconChevronLeft , { props : { shiftV : 0.5 , flipH : ! isRTL } } )
769806 const $nextYearIcon = h ( BIconChevronDoubleLeft , { props : { shiftV : 0.5 , flipH : ! isRTL } } )
807+ const $nextDecadeIcon = h ( BIconChevronBarLeft , { props : { shiftV : 0.5 , flipH : ! isRTL } } )
770808
771809 // Utility to create the date navigation buttons
772810 const makeNavBtn = ( content , label , handler , btnDisabled , shortcut ) => {
@@ -802,6 +840,15 @@ export const BCalendar = Vue.extend({
802840 }
803841 } ,
804842 [
843+ hideDecadeNav
844+ ? h ( )
845+ : makeNavBtn (
846+ $prevDecadeIcon ,
847+ this . labelPrevDecade ,
848+ this . gotoPrevDecade ,
849+ this . prevDecadeDisabled ,
850+ 'Ctrl+Alt+PageDown'
851+ ) ,
805852 makeNavBtn (
806853 $prevYearIcon ,
807854 this . labelPrevYear ,
@@ -836,7 +883,16 @@ export const BCalendar = Vue.extend({
836883 this . gotoNextYear ,
837884 this . nextYearDisabled ,
838885 'Alt+PageUp'
839- )
886+ ) ,
887+ hideDecadeNav
888+ ? h ( )
889+ : makeNavBtn (
890+ $nextDecadeIcon ,
891+ this . labelNextDecade ,
892+ this . gotoNextDecade ,
893+ this . nextDecadeDisabled ,
894+ 'Ctrl+Alt+PageUp'
895+ )
840896 ]
841897 )
842898
0 commit comments