< template >
< div id = "vue-datepicker" >
< div id = "dp-modal" @click.self ="hideModal" @ mousedown.self = " canCloseModal = true " v-if ="modalShow" >
< div id = "picker" >
< div class = "equal-width" id = "vuejs-tabs" >
< div : class = "tabIndex == 0?'active-tab':''" @ click = "tabIndex = 0;" >
{ { pTitle } }
< / div >
< div : class = "tabIndex == 1?'active-tab':''" @ click = "tabIndex = 1;" >
{ { gTitle } }
< / div >
< div v-if ="timepicker" :class="tabIndex == 2?'active-tab':''" @click="tabIndex = 2;" >
{ { tTitle } }
< / div >
< / div >
< div class = "equal-width" v-if ="pDate !== null && tabIndex < 2" >
< div @click ="next" class = "vuejsbtn" >
< i class = "ri-arrow-left-s-line" > < / i >
< / div >
< div @click ="monthPick" >
< span v-if ="tabIndex == 0" >
{ { pMonths [ parseInt ( peDate [ 1 ] ) - 1 ] } }
< / span >
< span v-if ="tabIndex == 1" >
{ { gMonths [ geDate [ 1 ] ] } }
< / span >
< / div >
< div @click ="yearPick" >
< span v-if ="tabIndex == 0" >
{ { pDate . parseHindi ( peDate [ 0 ] ) } }
< / span >
< span v-if ="tabIndex == 1" >
{ { geDate [ 0 ] } }
< / span >
< / div >
< div @click ="previous" class = "vuejsbtn" >
< i class = "ri-arrow-right-s-line" > < / i >
< / div >
< / div >
< div style = "overflow: hidden"
@ mousedown = "startSwipe($event)"
@ touchstart = "startSwipe($event, $event.touches[0])"
@ mousemove = "handleSwipe($event)"
@ touchmove = "handleSwipe($event, $event.touches[0])"
@ mouseup = "endSwipe($event)"
@ touchend = "endSwipe($event)"
@ mouseleave = "endSwipe($event)"
>
< div id = "calendar-container" :style ="`top:${calContainerTop}px;left:${calContainerLeft}px;`" >
< div class = "sub-picker" v-if ="yPicker" dir="rtl" >
< div class = "equal-width month-list" v-for ="j in 5" >
< template v-for ="i in 5" >
< template v-if ="i == 1 && j == 1" >
< div @ click = "startYear -= 23" >
< i class = "ri-arrow-right-s-line" > < / i >
< / div >
< / template >
< template v -else -if = " i = = 5 & & j = = 5 " >
< div @ click = "startYear += 23" >
< i class = "ri-arrow-left-s-line" > < / i >
< / div >
< / template >
< div v -else class = "year"
@ click = "yearPicking( (startYear - 13) + ( i + ((j - 1)*5)) )" >
< span v-if ="tabIndex == 0" >
{ { pDate . parseHindi ( ( startYear - 13 ) + ( i + ( ( j - 1 ) * 5 ) ) ) } }
< / span >
< span v-else >
{ { ( startYear - 13 ) + ( i + ( ( j - 1 ) * 5 ) ) } }
< / span >
< / div >
< / template >
< / div >
< / div >
< div class = "sub-picker" v-if ="pmPicker" dir="rtl" >
< div class = "equal-width month-list" v-for ="(ms,j) in chunkArray(pMonths,3)" >
< div v-for ="(m,i) in ms" class="month" @click="pMonthPicking((i + (j * 3)))" >
{ { m } }
< / div >
< / div >
< / div >
< div class = "sub-picker" v-if ="gmPicker" >
< div class = "equal-width month-list" v-for ="(ms,j) in chunkArray(gMonths,3)" >
< div v-for ="(m,i) in ms" class="month" @click="gMonthPicking((i + (j * 3)))" >
{ { m } }
< / div >
< / div >
< / div >
< div v-if ="tabIndex == 0 || _debug" dir="rtl" >
< table >
< thead >
< tr >
< th v-for ="day in pWeekDays" >
{ { day } }
< / th >
< / tr >
< / thead >
< tbody v-if ="pDate !== null" >
< tr v-for ="week in pArray" >
< td v -for = " d in week " : class = "d.class +' '+ isActive(d)" :title ="d.date"
@ click = "select(d)" >
{ { pDate . parseHindi ( d . pDay ) } }
< / td >
< / tr >
< / tbody >
< / table >
< / div >
< div v-if ="tabIndex == 1 || _debug" >
< table >
< thead >
< tr >
< th v-for ="day in gWeekDays" >
{ { day } }
< / th >
< / tr >
< / thead >
< tbody v-if ="pDate !== null" >
< tr v-for ="week in gArray" >
< td v -for = " d in week " : class = "d.class +' '+ isActive(d) " :title ="d.pdate"
@ click = "select(d)" >
{ { d . day } }
< / td >
< / tr >
< / tbody >
< / table >
< / div >
< div v-if ="tabIndex == 2" >
< div style = "padding-top: 10px" >
< div id = "clock"
@ mousedown = "startDrag"
@ mousemove = "pickMinutes"
@ mouseup = "endDrag"
@ touchstart = "startDrag"
@ touchmove . prevent = "pickMinutes"
@ touchend = "endDrag"
>
< div id = "modes" >
< div : class = "`vuejs-btn ${mode == 'AM'?'active-selected':''}`"
@ click = "changeMode('AM')"
@ touchend = "changeMode('AM')"
>
AM
< / div >
< div : class = "`vuejs-btn ${mode == 'PM'?'active-selected':''}`"
@ click = "changeMode('PM')"
@ touchend = "changeMode('PM')"
>
PM
< / div >
< / div >
< div id = "time" >
{ { pDate . make2number ( cTime [ 0 ] ) } } :
{ { pDate . make2number ( cTime [ 1 ] ) } }
< / div >
< div id = "clock-container" >
< div class = "wrapper" >
< div id = "circle" > < / div >
< div class = "bar-seconds" >
< span v-for ="i in 60" :key="i" :style="{ '--index': i }" >
< i : class = "{ 'thick-bar': i % 5 === 0 }" > < / i >
< / span >
< / div >
< div class = "number-hours" >
< span v-for ="i in 12" :key="i" :style="{ '--index': i }" >
< i >
< b @touchend.self ="pickHour(i)" @click ="pickHour(i)"
: class = "((cTime[0] % 12) == i?'active-selected':'')" >
{ { i } }
< / b >
< / i >
< / span >
< / div >
< div class = "hands-box" >
< div class = "hand minutes"
: style = "`transform: rotate(${cTime[1] * 6}deg)`" >
< i > < / i > < / div >
< div class = "hand hours"
: style = "`transform: rotate(${cTime[0] * 30 + cTime[1] / 2}deg)`" >
< i > < / i > < / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
< div id = "bottom-bar" >
< div >
< div class = "vuejs-btn" title = "Clear" @click ="clear" >
< i class = "ri-eraser-line" > < / i >
< / div >
< / div >
< div class = "centered" >
< span v-if ="pDate != null && '1970-0-1' != vgeDate.join('-')" >
[ { { pDate . parseHindi ( vpeDate . join ( '/' ) ) } } ] [ { { vgeDate . join ( '-' ) } } ]
< / span >
< / div >
< div >
< div class = "vuejs-btn" title = "Now" @click ="nowSelect" >
< i class = "ri-time-line" > < / i >
< / div >
< / div >
< / div >
< / div >
< / div >
< div id = "datepicker" >
< input @ focus = "modalShow = true" :id ="xid" :placeholder ="xtitle"
: class = "getClass" type = "text"
: value = "(val == null || val == ''?'':selectedDateTime)" >
< input type = "hidden" :name ="xname" :value ="val" >
< / div >
< / div >
< / template >
< script >
import persianDate from './libs/persian-date.js' ;
const ONE _DAY = 86400 ;
const ONE _YEAR = ONE _DAY * 365 ;
function chunkArray ( arr , count ) {
const result = [ ] ;
for ( let i = 0 ; i < arr . length ; i += count ) {
result . push ( arr . slice ( i , i + count ) ) ;
}
return result ;
}
export default {
name : "vue-datetimepicker" ,
components : { } ,
data : ( ) => {
return {
_debug : false , // debug all tabs
/ * *
* to handling swiping calendar
* /
startX : 0 ,
startY : 0 ,
swipeDirection : null ,
swipeDistance : 0 ,
tableRect : null ,
calContainerTop : 0 ,
calContainerLeft : 0 ,
isSwiping : false ,
canCloseModal : false ,
/ * *
* to handling dragging clock
* /
isDragging : false ,
fullData : { } , // full data of this date
modalShow : false , // modal handle
pDate : null , // persian date update
startYear : 1970 , // start year for year picking
tabIndex : 0 , // active tab index
current : null , // current is not selected value just for show calendar
gmPicker : false , // handle gr month picker modal
pmPicker : false , // handle persian month picker modal
yPicker : false , // handle year picker modal
val : null , // selected value
pWeekDays : [ 'ش' , 'ی' , 'د' , 'س' , 'چ' , 'پ' , 'آ' ] ,
gWeekDays : [ 'Su' , 'Mo' , 'Tu' , 'We' , 'Th' , 'Fr' , 'Sa' ] ,
pMonths : [ 'فروردین' , 'اردیبهشت' , 'خرداد' , 'تیر' , 'مرداد' , 'شهریور' , 'مهر' , 'آبان' , 'آذر' , 'دی' , 'بهمن' , 'اسفند' ] ,
gMonths : [ 'January' , 'February' , 'March' , 'April' , 'May' , 'June' , 'July' , 'August' , 'September' , 'October' , 'November' , 'December' ] ,
}
} ,
emits : [ 'update:modelValue' ] ,
props : {
modelValue : {
default : NaN ,
} ,
xvalue : {
default : null ,
type : Number ,
} ,
xmax : {
default : null ,
type : Number ,
} ,
xmin : {
default : null ,
type : Number ,
} ,
xshow : {
default : 'pdate' , // show value
type : String ,
} ,
onSelect : {
default : function ( date ) {
} ,
type : Function ,
} ,
pTitle : {
default : 'Persian' ,
type : String ,
} ,
gTitle : {
default : 'Gregorian' ,
type : String ,
} ,
tTitle : {
default : 'Time' ,
type : String ,
} ,
defTab : {
default : 0 ,
type : Number ,
} ,
xname : {
default : "" ,
type : String ,
} ,
xtitle : {
default : "" ,
type : String ,
} ,
xid : {
default : "" ,
type : String ,
} ,
customClass : {
default : "" ,
type : String ,
} ,
err : {
default : false ,
type : Boolean ,
} ,
timepicker : {
default : false ,
type : Boolean ,
} ,
closeOnSelect : {
default : false ,
type : Boolean ,
} ,
} ,
mounted ( ) {
this . pDate = new persianDate ( ) ;
let dt ;
// check value changed by user or not, then ignore xvalue
if ( this . val == null ) {
if ( ! isNaN ( this . modelValue ) ) {
dt = new Date ( parseInt ( this . modelValue ) * 1000 ) ;
if ( this . modelValue == null || this . modelValue == '' || this . modelValue == 'null' ) {
dt = new Date ( ) ;
this . val = null ;
this . current = Math . floor ( new Date ( ) / 1000 ) ;
} else {
this . current = new Date ( parseInt ( this . modelValue ) ) ;
this . val = this . modelValue ;
}
} else {
dt = new Date ( parseInt ( this . xvalue ) * 1000 ) ;
if ( this . xvalue == null || this . xvalue == '' || this . xvalue == 'null' ) {
dt = new Date ( ) ;
this . val = null ;
this . current = Math . floor ( new Date ( ) / 1000 ) ;
} else {
this . current = new Date ( parseInt ( this . xvalue ) ) ;
this . val = this . xvalue ;
}
}
// tab fix
this . tabIndex = parseInt ( this . defTab ) ;
} else {
this . current = this . val ;
}
this . fullData = this . makeDateObject ( dt )
// if (this.xvalue != this.val){
//
// }
} ,
computed : {
selectedDateTime ( ) {
// fullData[xshow]
const dt = new Date ( this . val * 1000 ) ;
return this . makeDateObject ( dt ) [ this . xshow ] ;
} ,
// get input class
getClass : function ( ) {
if ( this . err == true || ( typeof this . err == 'String' && this . err . trim ( ) == '1' ) ) {
return 'form-control is-invalid text-center' + this . customClass ;
}
return 'form-control text-center ' + this . customClass ;
} ,
/ *
* make array of this month days [ gregorian ]
* /
gArray : function ( ) {
let result = [ ] ;
const baseDate = this . current * 1000 ;
let d = new Date ( baseDate ) ;
const currentMonth = d . getMonth ( baseDate ) ;
// find days of last month end by dayweek
for ( let i = 0 ; i >= - 7 ; i -- ) {
d = new Date ( baseDate ) ;
d . setDate ( i ) ;
result . push ( this . makeDateObject ( d , 'previous' ) ) ;
if ( d . getDay ( ) == 0 ) {
break ;
}
}
result = result . reverse ( ) ; // fix sort
let nextCount = 0 ;
// find days of this month and start of next end by dayweek
for ( let i = 1 ; i <= 45 ; i ++ ) {
d = new Date ( baseDate ) ;
d . setDate ( i ) ;
if ( d . getMonth ( ) == currentMonth ) {
result . push ( this . makeDateObject ( d ) ) ;
} else {
if ( d . getDay ( ) == 0 && nextCount > 0 ) {
break ;
}
result . push ( this . makeDateObject ( d , 'next' ) ) ;
nextCount ++ ;
}
}
return chunkArray ( result , 7 ) ;
} ,
/ *
* make array of this month days [ persian ]
* /
pArray : function ( ) {
let result = [ ] ;
const baseDate = this . current * 1000 ;
let d = this . pDate . convertDate2Persian ( new Date ( baseDate ) ) ;
const currentMonth = d [ 1 ] ;
// get current date to prev until last month end by day week
for ( let i = 0 ; i > - 40 ; i -- ) {
let dt = new Date ( baseDate + ( i * ONE _DAY * 1000 ) ) ;
let pdt = this . pDate . convertDate2Persian ( dt ) ;
if ( pdt [ 1 ] == currentMonth ) {
result . push ( this . makeDateObject ( dt ) ) ;
} else {
result . push ( this . makeDateObject ( dt , 'previous' ) ) ;
if ( this . makePWeek ( dt ) == 0 ) {
break ;
}
}
}
// fix sort
result = result . reverse ( ) ;
// get current date to next until next month end by day week
for ( let i = 1 ; i < 40 ; i ++ ) {
let dt = new Date ( baseDate + ( i * ONE _DAY * 1000 ) ) ;
let pdt = this . pDate . convertDate2Persian ( dt ) ;
if ( pdt [ 1 ] == currentMonth ) {
result . push ( this . makeDateObject ( dt ) ) ;
} else {
result . push ( this . makeDateObject ( dt , 'next' ) ) ;
if ( this . makePWeek ( dt ) == 6 ) {
break ;
}
}
}
return chunkArray ( result , 7 ) ;
} ,
// gregorian date
geDate ( ) {
const baseDate = this . current * 1000 ;
let d = new Date ( baseDate ) ;
return [ d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) ] ;
} ,
// persian date
peDate ( ) {
const baseDate = this . current * 1000 ;
let d = new Date ( baseDate ) ;
return this . pDate . convertDate2Persian ( d ) ;
} ,
// gregorian date by value
vgeDate ( ) {
const baseDate = this . val * 1000 ;
let d = new Date ( baseDate ) ;
return [ d . getFullYear ( ) , d . getMonth ( ) , d . getDate ( ) ] ;
} ,
// persian date by value
vpeDate ( ) {
const baseDate = this . val * 1000 ;
let d = new Date ( baseDate ) ;
return this . pDate . convertDate2Persian ( d ) ;
} ,
// current time
cTime ( ) {
const baseDate = this . val * 1000 ;
let d = new Date ( baseDate ) ;
return [ d . getHours ( ) , d . getMinutes ( ) ] ;
} ,
mode ( ) {
const date = new Date ( this . val * 1000 ) ; // Convert Unix timestamp to milliseconds
const hours = date . getHours ( ) ;
if ( hours >= 12 ) {
return 'PM' ;
} else {
return 'AM' ;
}
} ,
} ,
methods : {
// clear input
clear ( ) {
this . hideModal ( ) ;
this . val = null ;
this . fullData [ this . xshow ] = '' ;
} ,
// select now time
nowSelect ( ) {
this . val = Math . floor ( new Date ( ) / 1000 ) ;
this . select ( this . makeDateObject ( new Date ( ) ) ) ;
} ,
// handle select
select ( obj ) {
if ( this . xmax != null && obj . unix > this . xmax ) {
return ;
}
if ( this . xmin != null && obj . unix < this . xmin ) {
return ;
}
if ( this . isSwiping ) {
return false ;
}
if ( obj . class == 'next' ) {
this . next ( ) ;
return false ;
}
if ( obj . class == 'previous' ) {
this . previous ( ) ;
return false ;
}
// reset values
this . onSelect ( obj ) ;
this . val = obj . unix ;
this . fullData = obj ;
this . current = this . val = obj . unix ;
if ( this . closeOnSelect ) {
this . canCloseModal = true ;
this . hideModal ( ) ;
}
return true ;
} ,
// select year
yearPicking ( i ) {
let dt = this . current * 1000 ;
dt = new Date ( dt ) ;
// for gregorian
if ( this . tabIndex == 1 ) {
dt . setFullYear ( i ) ;
this . current = Math . floor ( dt / 1000 ) ;
} else {
// for persian
let cYear = parseInt ( this . peDate [ 0 ] ) ;
let diff = ONE _YEAR * ( i - cYear ) ;
this . current = Math . floor ( ( dt / 1000 ) + diff ) ;
}
this . yPicker = false ;
} ,
// change gregorian current month
gMonthPicking ( i ) {
let dt = this . current * 1000 ;
dt = new Date ( dt ) ;
dt . setMonth ( parseInt ( i ) ) ;
this . current = Math . floor ( dt / 1000 ) ;
this . gmPicker = false ;
} ,
// change persian current month
pMonthPicking ( i ) {
let dt = this . current * 1000 ;
dt = new Date ( dt ) ;
let x = 10 ;
if ( ( i + 1 ) != parseInt ( this . peDate [ 1 ] ) ) {
if ( ( i + 1 ) < parseInt ( this . peDate [ 1 ] ) ) {
x = - 10 ;
}
// for persian find by loop for dec tolerance
do {
dt . setDate ( dt . getDate ( ) + x ) ;
} while ( ( i + 1 ) != this . pDate . convertDate2Persian ( dt ) [ 1 ] ) ;
this . current = Math . floor ( dt / 1000 ) ;
}
this . pmPicker = false ;
} ,
// next month
next ( ) {
let dt = this . current * 1000 ;
dt = new Date ( dt ) ;
// for gregorian
if ( this . tabIndex == 1 ) {
dt . setMonth ( dt . getMonth ( ) + 1 ) ;
} else {
let currentMonth = this . pDate . convertDate2Persian ( new Date ( dt ) ) [ 1 ] ;
// for persian find by loop for dec tolerance
do {
dt . setDate ( dt . getDate ( ) + 10 ) ;
} while ( currentMonth == this . pDate . convertDate2Persian ( dt ) [ 1 ] ) ;
}
this . current = Math . floor ( dt / 1000 ) ;
} ,
// previous month
previous ( ) {
let dt = this . current * 1000 ;
dt = new Date ( dt ) ;
// for gregorian
if ( this . tabIndex == 1 ) {
dt . setMonth ( dt . getMonth ( ) - 1 ) ;
} else {
// persian
let currentMonth = this . pDate . convertDate2Persian ( new Date ( dt ) ) [ 1 ] ;
// for persian find by loop for dec tolerance
do {
dt . setDate ( dt . getDate ( ) - 10 ) ;
} while ( currentMonth == this . pDate . convertDate2Persian ( dt ) [ 1 ] ) ;
}
this . current = Math . floor ( dt / 1000 ) ;
} ,
makeDateObject ( dt , cls ) {
dt . setHours ( this . cTime [ 0 ] , this . cTime [ 1 ] ) ;
return {
day : this . pDate . make2number ( dt . getDate ( ) ) , // day
pDay : this . pDate . convertDate2Persian ( dt ) [ 2 ] , // persian date
date : dt . getFullYear ( ) + '-' + dt . getMonth ( ) + '-' + dt . getDate ( ) , // gregorian date
datetime : dt . getFullYear ( ) + '-' + dt . getMonth ( ) + '-' + dt . getDate ( ) + ' ' + this . pDate . make2number ( this . cTime [ 0 ] ) + ':' + this . pDate . make2number ( this . cTime [ 1 ] ) , // gregorian datetime
pdatetime : this . pDate . convertDate2Persian ( dt ) . join ( '/' ) + ' ' + this . pDate . make2number ( this . cTime [ 0 ] ) + ':' + this . pDate . make2number ( this . cTime [ 1 ] ) , // persian date
pdate : this . pDate . convertDate2Persian ( dt ) . join ( '/' ) , // persian date
hpdatetime : this . pDate . parseHindi ( this . pDate . convertDate2Persian ( dt ) . join ( '/' ) + ' ' + this . pDate . make2number ( this . cTime [ 0 ] ) + ':' + this . pDate . make2number ( this . cTime [ 1 ] ) ) , // persian date hindi number
hpdate : this . pDate . parseHindi ( this . pDate . convertDate2Persian ( dt ) . join ( '/' ) ) , // persian date hindi number
weekDay : dt . getDay ( ) , // week day
class : cls , // class of d
unix : Math . floor ( dt / 1000 ) // unix time stamp
} ;
} ,
// make persian week day
makePWeek ( dt ) {
let t = dt . getDay ( ) + 1 % 7 ;
if ( t == 7 ) {
return 0
}
return t ;
} ,
// show month pick modal
monthPick ( ) {
// gregorian
if ( this . tabIndex == 1 ) {
this . gmPicker = ! this . gmPicker ;
this . pmPicker = false ;
} else {
// persian
this . pmPicker = ! this . pmPicker ;
this . gmPicker = false ;
}
} ,
// show year pick modal
yearPick ( ) {
// gregorian
if ( this . tabIndex == 1 ) {
this . startYear = parseInt ( this . geDate [ 0 ] ) ;
} else {
// persian
this . startYear = parseInt ( this . peDate [ 0 ] ) ;
}
this . yPicker = ! this . yPicker ;
} ,
// is selected this td
isActive ( obj ) {
let dt = new Date ( this . val * 1000 ) ;
let r = '' ;
if ( dt . getFullYear ( ) + '-' + dt . getMonth ( ) + '-' + dt . getDate ( ) == obj . date ) {
r = 'active-selected' ;
}
if ( this . xmax != null && obj . unix > this . xmax ) {
r += ' disabled-date' ;
}
if ( this . xmin != null && obj . unix < this . xmin ) {
r += ' disabled-date' ;
}
return r ;
} ,
// select hour
pickHour ( i , ignore = false ) {
let dt = new Date ( this . val * 1000 ) ;
if ( ignore ) {
dt . setHours ( i ) ;
} else {
dt . setHours ( ( this . mode == 'AM' ? i : ( i + 12 ) ) ) ;
}
dt . setMinutes ( this . cTime [ 1 ] ) ;
this . val = Math . floor ( dt . getTime ( ) / 1000 ) ;
} ,
/ * *
* drag handling for clock select
* /
startDrag ( e ) {
if ( e . target . tagName != 'B' ) {
this . isDragging = true ;
}
this . dragHandle ( e ) ;
} ,
pickMinutes ( e ) {
if ( ! this . isDragging ) return ;
this . dragHandle ( e ) ;
} ,
endDrag ( e ) {
this . isDragging = false ;
this . dragHandle ( e ) ;
} ,
dragHandle ( e ) {
e . preventDefault ( ) ;
if ( ! this . isDragging ) {
return ;
}
// calc polar system delta
const touch = e . touches ? e . touches [ 0 ] : null ;
const eventX = touch ? touch . clientX : e . clientX ;
const eventY = touch ? touch . clientY : e . clientY ;
const rect = e . currentTarget . getBoundingClientRect ( ) ;
const centerX = rect . left + rect . width / 2 ;
const centerY = rect . top + rect . height / 2 ;
const deltaX = eventX - centerX ;
const deltaY = eventY - centerY ;
const r = Math . sqrt ( deltaX * * 2 + deltaY * * 2 ) ;
let theta = Math . atan2 ( deltaY , deltaX ) ;
theta = ( ( theta * 180 / Math . PI ) + 450 ) % 360 ;
// console.log('r:', r);
// console.log('theta:', theta);
if ( r > 90 && r < 160 ) {
const minutes = Math . floor ( ( theta / 360 ) * 60 ) ;
let dt = new Date ( this . val * 1000 ) ;
dt . setHours ( dt . getHours ( ) ) ;
dt . setMinutes ( minutes ) ;
this . val = Math . floor ( dt / 1000 ) ;
}
} ,
/ * *
* swipe calendar next / perv ( month / year )
* /
startSwipe ( e , touch ) {
this . isSwiping = true ;
this . canCloseModal = false ;
this . contentRect = e . currentTarget . getBoundingClientRect ( ) ;
this . startX = touch ? touch . clientX : e . clientX ;
this . startY = touch ? touch . clientY : e . clientY ;
this . swipeDirection = null ;
this . swipeDistance = 0 ;
this . hasSwipedOnce = false ; // Reset the swipe flag
} ,
handleSwipe ( e , touch ) {
e . preventDefault ( ) ;
if ( this . tabIndex == 2 ) {
return false ;
}
if ( ! this . startX || ! this . startY ) return ;
const currentX = touch ? touch . clientX : e . clientX ;
const currentY = touch ? touch . clientY : e . clientY ;
const deltaX = currentX - this . startX ;
const deltaY = currentY - this . startY ;
if ( Math . abs ( deltaY ) > Math . abs ( deltaX ) ) {
this . calContainerTop = deltaY * .5 ;
this . calContainerLeft = 0 ;
}
if ( Math . abs ( deltaY ) < Math . abs ( deltaX ) ) {
this . calContainerTop = 0 ;
this . calContainerLeft = deltaX * .5 ;
}
this . swipeDirection = Math . abs ( deltaX ) > Math . abs ( deltaY )
? deltaX > 0 ? 'right' : 'left'
: deltaY > 0 ? 'down' : 'up' ;
this . swipeDistance = this . swipeDirection === 'right' || this . swipeDirection === 'left'
? Math . abs ( deltaX )
: Math . abs ( deltaY ) ;
// Update content padding based on swipe distance and direction (handle 40%)
if ( this . swipeDistance > this . contentRect . width * 0.4 && ! this . hasSwipedOnce ) {
this . triggerSwipe ( this . swipeDirection ) ;
this . hasSwipedOnce = true ; // Set the swipe flag to true
}
} ,
endSwipe ( ) {
this . startX = 0 ;
this . startY = 0 ;
this . swipeDirection = null ;
this . swipeDistance = 0 ;
this . calContainerTop = 0 ;
this . calContainerLeft = 0 ;
this . isSwiping = false ;
} ,
triggerSwipe ( direction ) {
// Update content padding based on swipe direction
let y = parseInt ( this . peDate [ 0 ] ) ;
if ( this . tabIndex == 1 ) {
y = parseInt ( this . geDate [ 1 ] ) ;
}
switch ( direction ) {
case 'right' :
this . previous ( ) ;
break ;
case 'left' :
this . next ( ) ;
break ;
case 'up' :
this . yearPicking ( y + 1 ) ;
break ;
case 'down' :
this . yearPicking ( y - 1 ) ;
break ;
}
this . endSwipe ( ) ;
} ,
// change mode am/pm
changeMode ( mode ) {
// ignore AM while AM
if ( this . mode == 'AM' && mode == 'AM' ) {
return ;
}
// ignore PM while PM
if ( this . mode == 'PM' && mode == 'PM' ) {
return ;
}
if ( mode == 'AM' ) {
if ( this . cTime [ 0 ] == 12 ) {
this . pickHour ( 12 ) ;
} else {
this . pickHour ( this . cTime [ 0 ] - 12 , true ) ;
}
} else {
this . pickHour ( this . cTime [ 0 ] + 12 , true ) ;
}
} ,
selfUpdate ( ) {
let dt ;
// check value changed by user or not, then ignore xvalue
if ( this . val == null ) {
dt = new Date ( parseInt ( this . xvalue ) * 1000 ) ;
if ( this . xvalue == null || this . xvalue == '' || this . xvalue == 'null' ) {
dt = new Date ( ) ;
this . val = null ;
this . current = Math . floor ( new Date ( ) / 1000 ) ;
} else {
this . current = new Date ( parseInt ( this . xvalue ) ) ;
this . val = this . xvalue ;
}
} else {
this . current = this . val ;
}
// this.fullData = this.makeDateObject(dt);
} ,
// hide modal
hideModal ( ) {
if ( this . canCloseModal ) {
this . modalShow = false ;
}
} ,
chunkArray : chunkArray ,
} ,
watch : {
val ( newValue ) {
if ( ! isNaN ( this . modelValue ) ) {
this . $emit ( 'update:modelValue' , newValue ) ;
}
}
}
}
< / script >
< style scoped >
# vue - datepicker {
font - size : 12 pt ;
direction : ltr ;
}
# dp - modal {
position : fixed ;
//display: none; left: 0; right: 0; top: 0; bottom: 0; z-index: 999; background: #00000033; backdrop-filter: blur(4px);
}
# picker {
max - width : 400 px ;
min - height : 450 px ;
margin : calc ( 50 vh - 225 px ) auto ;
background : # ffffffdd ;
backdrop - filter : blur ( 4 px ) ;
user - select : none ;
color : black ;
padding : 5 px ;
font - family : 'Vazir' , 'Vazirmatn' , sans - serif ;
}
# picker table {
border : 1 px solid black ;
width : 100 % ;
margin - top : 5 px ;
}
# picker table td , # picker table th {
border : 1 px solid silver ;
width : calc ( 100 % / 7 ) ;
text - align : center ;
padding : 7 px ;
transition : 500 ms ;
}
# picker table td : hover {
background : deepskyblue ;
cursor : pointer ;
}
# picker . next , # picker . previous {
color : gray ;
}
. equal - width {
display : grid ;
grid - auto - columns : minmax ( 0 , 1 fr ) ;
grid - auto - flow : column ;
text - align : center ;
cursor : pointer ;
}
. equal - width div {
padding : .5 rem 5 px ;
font - weight : 800 ;
}
. equal - width div i {
font - size : 25 px ;
}
# bottom - bar {
display : grid ;
grid - template - columns : 1 fr 2 fr 1 fr ;
text - align : center ;
}
# bottom - bar > div {
padding : 7 px 4 px ;
}
# vuejs - tabs {
border : 1 px solid gray ;
margin - bottom : .5 rem ;
}
. equal - width div : hover {
background : teal ;
color : white ; ;
}
. active - tab {
background : deepskyblue ;
color : white ;
}
. vuejsbtn {
padding : 1 px ! important ;
}
# calendar - container {
position : relative ;
min - height : 285 px ;
}
. sub - picker {
position : absolute ;
left : 0 ;
top : 0 ;
bottom : 0 ;
right : 0 ;
background : white ;
}
. month {
padding : 1.4 rem 0 ! important ;
}
. year {
padding : .97 rem 0 ! important ;
}
. vuejs - btn {
padding : 3 px ;
background : silver ;
display : block ;
cursor : pointer ;
}
. vuejs - btn : hover {
background : deepskyblue ;
color : white ;
}
. vuejs - btn i {
font - size : 20 px ;
}
. centered {
display : flex ;
align - items : center ;
justify - content : center ;
}
. centered span {
margin - top : 6 px ;
display : inline - block ;
}
. active - selected {
background : teal ;
color : yellow ! important ;
}
# clock {
padding : 33.5 px 65 px ;
}
# circle {
position : absolute ;
background : deepskyblue ;
width : 6 px ;
height : 6 px ;
left : calc ( 50 % - 3 px ) ;
top : calc ( 50 % - 3 px ) ;
border - radius : 50 % ;
z - index : 5 ;
}
. wrapper {
position : relative ;
width : 255 px ;
height : 255 px ;
border - radius : 50 % ;
display : flex ;
justify - content : center ;
align - items : center ;
}
. bar - seconds ,
. number - hours {
position : absolute ;
width : 100 % ;
height : 100 % ;
border - radius : 50 % ;
}
. bar - seconds span {
position : absolute ;
transform : rotate ( calc ( var ( -- index ) * 6 deg ) ) ;
inset : - 20 px ;
text - align : center ;
pointer - events : none ;
}
. bar - seconds span i {
display : inline - block ;
width : 2 px ;
height : 12 px ;
background : deepskyblue ;
border - radius : 2 px ;
box - shadow : 0 0 10 px deepskyblue ;
pointer - events : none ;
font - style : normal ;
}
. bar - seconds span : nth - child ( 5 n ) i { /* 5n pour dire tout les mutliples de 5 */
width : 6 px ;
height : 18 px ;
transform : translateY ( 1 px ) ;
}
. number - hours span {
position : absolute ;
transform : rotate ( calc ( var ( -- index ) * 30 deg ) ) ;
inset : 6 px ;
text - align : center ;
pointer - events : none ;
}
. number - hours span i {
font - size : 25 px ;
color : deepskyblue ;
transform : rotate ( calc ( var ( -- index ) * - 30 deg ) ) ;
pointer - events : none ;
font - style : normal ;
}
. number - hours span i b {
text - decoration : none ;
color : deepskyblue ;
pointer - events : all ;
display : inline - block ;
width : 40 px ;
border - radius : 50 % ;
height : 40 px ;
box - sizing : border - box ;
padding : 2 px ;
font - weight : 400 ;
}
. number - hours span i b : hover {
background : teal ;
color : white ;
}
. hands - box {
position : relative ;
display : flex ;
justify - content : center ;
align - items : center ;
pointer - events : none ;
}
. hands - box . hand {
position : absolute ;
border - radius : 50 % ;
display : flex ;
justify - content : center ;
}
. hands - box . hand i {
display : inline - block ;
transform - origin : bottom ;
border - radius : 50 % ;
box - shadow : 0 0 10 px deepskyblue ;
}
. hands - box . hours {
width : 180 px ;
height : 180 px ;
}
. hands - box . hours i {
width : 8 px ;
height : 90 px ;
background : deepskyblue ;
}
. hands - box . minutes {
width : 275 px ;
height : 275 px ;
}
. hands - box . minutes i {
width : 6 px ;
height : 140 px ;
background : dodgerblue ;
border - radius : 2 px ;
}
# modes {
position : absolute ;
left : 5 px ;
top : 5 px ;
}
# modes . vuejs - btn {
padding : 5 px ;
width : 40 px ;
text - align : center ;
padding - top : 10 px ;
}
# time {
position : absolute ;
right : 5 px ;
top : 5 px ;
font - size : 25 px ;
}
. disabled - date {
background : silver ;
}
< / style >