Skip to content

Commit e26e1f5

Browse files
committed
feat: add prop appendToBody
1 parent 8ebb016 commit e26e1f5

File tree

7 files changed

+104
-14
lines changed

7 files changed

+104
-14
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export default {
8181
| disabled | Boolean | false | Disable the component |
8282
| placeholder | String | | input placeholder text |
8383
| width | String/Number | 210 | input size |
84+
| append-to-body | Boolean | false | append the popup to body |
85+
| popupStyle | Object | | popup style(override the top, left style) |
8486
| not-before | String/Date | '' | Disable all dates before new Date(not-before) |
8587
| not-after | String/Date | '' | Disable all dates after new Date(not-after) |
8688
| disabled-days | Array/function| [] | Disable Days |

README.zh-CN.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ export default {
8080
| editable | Boolean | true | 如果是false, 用户不能手动输入更新日期
8181
| disabled | Boolean | false | 禁用组件
8282
| placeholder | String | | 输入框placeholder
83-
| width | String/Number | 210 | 设置宽度
83+
| width | String/Number | 210 | 设置宽度
84+
| append-to-body | Boolean | false | 弹出层放到body下面
85+
| popup-style | Object | | 弹出层的样式(可以覆盖left,top样式)
8486
| not-before | String/Date | '' | 禁止选择这个时间之前的时间
8587
| not-after | String/Date | '' | 禁止选择这个时间之前=后的时间
8688
| disabled-days | Array/function| [] | 自定义禁止的日期

demo/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ new Vue({ // eslint-disable-line
4747
render (h) {
4848
const example1 = {
4949
'base': '<date-picker v-model="value1" lang="en" :not-before="new Date()"></date-picker>',
50-
'range': '<date-picker v-model="value2" range ></date-picker>',
50+
'range': '<date-picker v-model="value2" range appendToBody></date-picker>',
5151
'month': '<date-picker v-model="value10" lang="en" type="month" format="YYYY-MM"></date-picker>',
5252
'year': '<date-picker v-model="value11" lang="en" type="year" format="YYYY"></date-picker>',
5353
'time': '<date-picker v-model="value12" lang="en" type="time" format="HH:mm:ss" placeholder="Select Time"></date-picker>'
@@ -64,6 +64,7 @@ new Vue({ // eslint-disable-line
6464
v-model="value4"
6565
lang="en"
6666
type="datetime"
67+
appendToBody
6768
format="YYYY-MM-DD hh:mm:ss a"
6869
:time-picker-options="{
6970
start: '00:00',

src/directives/clickoutside.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export default {
44
el['@clickoutside'] = e => {
55
if (
66
!el.contains(e.target) &&
7+
!(vnode.context.popupElm && vnode.context.popupElm.contains(e.target)) &&
78
binding.expression &&
89
vnode.context[binding.expression]
910
) {

src/index.vue

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
</span>
4545
</div>
4646
<div class="mx-datepicker-popup"
47-
:style="position"
47+
:style="innerPopupStyle"
4848
v-show="popupVisible"
4949
ref="calendar">
5050
<slot name="header">
@@ -106,7 +106,7 @@
106106
<script>
107107
import fecha from 'fecha'
108108
import clickoutside from '@/directives/clickoutside'
109-
import { isValidDate, isValidRange, isDateObejct, isPlainObject, formatDate, parseDate } from '@/utils/index'
109+
import { isValidDate, isValidRange, isDateObejct, isPlainObject, formatDate, parseDate, throttle } from '@/utils/index'
110110
import CalendarPanel from './calendar.vue'
111111
import locale from '@/mixins/locale'
112112
import Languages from '@/locale/languages'
@@ -183,6 +183,13 @@ export default {
183183
inputClass: {
184184
type: [String, Array],
185185
default: 'mx-input'
186+
},
187+
appendToBody: {
188+
type: Boolean,
189+
default: false
190+
},
191+
popupStyle: {
192+
type: Object
186193
}
187194
},
188195
data () {
@@ -290,8 +297,31 @@ export default {
290297
return this.format
291298
}
292299
return this.format.replace(/[Hh]+.*[msSaAZ]|\[.*?\]/g, '').trim() || 'YYYY-MM-DD'
300+
},
301+
innerPopupStyle () {
302+
return { ...this.position, ...this.popupStyle }
293303
}
294304
},
305+
mounted () {
306+
if (this.appendToBody) {
307+
this.popupElm = this.$refs.calendar
308+
document.body.appendChild(this.popupElm)
309+
}
310+
this._displayPopup = throttle(() => {
311+
if (this.popupVisible) {
312+
this.displayPopup()
313+
}
314+
}, 200)
315+
window.addEventListener('resize', this._displayPopup)
316+
window.addEventListener('scroll', this._displayPopup)
317+
},
318+
beforeDestroy () {
319+
if (this.popupElm && this.popupElm.parentNode === document.body) {
320+
document.body.removeChild(this.popupElm)
321+
}
322+
window.removeEventListener('resize', this._displayPopup)
323+
window.removeEventListener('scroll', this._displayPopup)
324+
},
295325
methods: {
296326
initCalendar () {
297327
this.handleValueChange(this.value)
@@ -384,31 +414,53 @@ export default {
384414
closePopup () {
385415
this.popupVisible = false
386416
},
417+
getPopupSize (element) {
418+
const originalDisplay = element.style.display
419+
const originalVisibility = element.style.visibility
420+
element.style.display = 'block'
421+
element.style.visibility = 'hidden'
422+
const styles = window.getComputedStyle(element)
423+
const width = element.offsetWidth + parseInt(styles.marginLeft) + parseInt(styles.marginRight)
424+
const height = element.offsetHeight + parseInt(styles.marginTop) + parseInt(styles.marginBottom)
425+
const result = { width, height }
426+
element.style.display = originalDisplay
427+
element.style.visibility = originalVisibility
428+
return result
429+
},
387430
displayPopup () {
388431
const dw = document.documentElement.clientWidth
389432
const dh = document.documentElement.clientHeight
390433
const InputRect = this.$el.getBoundingClientRect()
391-
const PopupRect = this.$refs.calendar.getBoundingClientRect()
392-
this.position = {}
434+
const PopupRect = this._popupRect || (this._popupRect = this.getPopupSize(this.$refs.calendar))
435+
const position = {}
436+
let offsetRelativeToInputX = 0
437+
let offsetRelativeToInputY = 0
438+
if (this.appendToBody) {
439+
offsetRelativeToInputX = window.pageXOffset + InputRect.left
440+
offsetRelativeToInputY = window.pageYOffset + InputRect.top
441+
}
393442
if (
394443
dw - InputRect.left < PopupRect.width &&
395444
InputRect.right < PopupRect.width
396445
) {
397-
this.position.left = 1 - InputRect.left + 'px'
446+
position.left = offsetRelativeToInputX - InputRect.left + 1 + 'px'
398447
} else if (InputRect.left + InputRect.width / 2 <= dw / 2) {
399-
this.position.left = 0
448+
position.left = offsetRelativeToInputX + 'px'
400449
} else {
401-
this.position.right = 0
450+
position.left = offsetRelativeToInputX + InputRect.width - PopupRect.width + 'px'
402451
}
403452
if (
404-
InputRect.top <= PopupRect.height + 1 &&
405-
dh - InputRect.bottom <= PopupRect.height + 1
453+
InputRect.top <= PopupRect.height &&
454+
dh - InputRect.bottom <= PopupRect.height
406455
) {
407-
this.position.top = dh - InputRect.top - PopupRect.height - 1 + 'px'
456+
position.top = offsetRelativeToInputY + dh - InputRect.top - PopupRect.height + 'px'
408457
} else if (InputRect.top + InputRect.height / 2 <= dh / 2) {
409-
this.position.top = '100%'
458+
position.top = offsetRelativeToInputY + InputRect.height + 'px'
410459
} else {
411-
this.position.bottom = '100%'
460+
position.top = offsetRelativeToInputY - PopupRect.height + 'px'
461+
}
462+
if (position.top !== this.position.top || position.left !== this.position.left) {
463+
this.position = position
412464
}
413465
},
414466
handleInput (event) {

src/utils/index.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,25 @@ export function parseDate (value, format) {
6969
return false
7070
}
7171
}
72+
73+
export function throttle (action, delay) {
74+
let lastRun = 0
75+
let timeout = null
76+
return function () {
77+
if (timeout) {
78+
return
79+
}
80+
const args = arguments
81+
const elapsed = Date.now() - lastRun
82+
const callBack = () => {
83+
lastRun = Date.now()
84+
timeout = null
85+
action.apply(this, args)
86+
}
87+
if (elapsed >= delay) {
88+
callBack()
89+
} else {
90+
timeout = setTimeout(callBack, delay)
91+
}
92+
}
93+
}

test/index.spec.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ afterEach(() => {
1313
})
1414

1515
describe('datepicker', () => {
16+
it('prop: appendToBody', () => {
17+
wrapper = mount(DatePicker, {
18+
propsData: {
19+
appendToBody: true
20+
}
21+
})
22+
const popup = wrapper.find('.mx-datepicker-popup')
23+
expect(popup.element.parentNode).toBe(document.body)
24+
})
25+
1626
it('click: pick date', () => {
1727
wrapper = mount(DatePicker, {
1828
propsData: {

0 commit comments

Comments
 (0)