UNPKG

vanillajs-datepicker

Version:

A vanilla JavaScript remake of bootstrap-datepicker for Bulma and other CSS frameworks

1,342 lines (1,093 loc) 46.9 kB
describe('options - orientation', function () { const docElem = document.documentElement; const scrollToElem = (el, useBottom = false) => { const rect = el.getBoundingClientRect(); let newY = rect.top + window.scrollY; if (useBottom) { newY += Math.abs(docElem.clientHeight - rect.height); } window.scrollTo(window.scrollX, newY); }; const pushToBottom = (el) => { const gap = docElem.clientHeight - document.body.clientHeight; if (gap > 0) { el.style.marginTop = `${gap}px`; } }; const getRectsOf = (...elems) => new Promise((resolve) => { window.requestAnimationFrame(() => { resolve(elems.map(el => el.getBoundingClientRect())); }); }); const defaultWrapperStyle = { paddingTop: '300px', paddingRight: '20px', paddingBottom: '320px', paddingLeft: '20px', }; const resetPositioning = () => { outer.removeAttribute('style'); wrapper.removeAttribute('dir'); wrapper.removeAttribute('style'); Object.assign(wrapper.style, defaultWrapperStyle); }; let options; let outer; let wrapper; let input; beforeEach(function () { options = {}; outer = document.createElement('div'); outer.id = 'outer'; wrapper = document.createElement('div'); wrapper.id = 'wrapper'; Object.assign(wrapper.style, defaultWrapperStyle); input = document.createElement('input'); input.style.width = '160px'; wrapper.appendChild(input); outer.appendChild(wrapper); testContainer.appendChild(outer); }); afterEach(function () { if (input.datepicker) { input.datepicker.destroy(); } testContainer.removeChild(outer); }); const testAutoDefault = async function () { const {dp, picker} = createDP(input, options); scrollToElem(outer); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); Object.assign(outer.style, { position: 'fixed', top: '0', left: '0', width: '300px', }); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.destroy(); resetPositioning(); return Promise.resolve(); }; it('"auto" makes the picker show on left bottom of the input by default', testAutoDefault); const testAutoRtl = async function () { wrapper.setAttribute('dir', 'rtl'); const {dp, picker} = createDP(input, options); scrollToElem(outer); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); Object.assign(outer.style, { position: 'fixed', top: '0', left: '0', width: '300px', }); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.destroy(); resetPositioning(); return Promise.resolve(); }; it('"auto" makes the picker show on bottom right of the input if the computed style of the input has direction: rrl', testAutoRtl); const testAutoNoBottomSpace = async function () { const {dp, picker} = createDP(input, options); wrapper.style.paddingBottom = ''; pushToBottom(outer); scrollToElem(outer, true); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.hide(); wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.hide(); outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.hide(); const bottom = docElem.clientHeight > 659 ? 300 : docElem.clientHeight - 360; Object.assign(outer.style, { height: '360px', marginBottom: `${bottom}px`, overflow: 'auto', }); wrapper.style.paddingBottom = '320px'; wrapper.setAttribute('dir', 'ltr'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.hide(); outer.removeAttribute('style'); Object.assign(outer.style, { position: 'fixed', bottom: '0', left: '0', width: '300px', }); wrapper.style.paddingBottom = ''; scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.hide(); Object.assign(outer.style, { bottom: `${bottom}px`, height: '360px', overflow: 'auto', }); wrapper.style.paddingBottom = '320px'; wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.destroy(); resetPositioning(); return Promise.resolve(); }; it('"auto" makes the picker show on top of the input if the visible space below the input < picker height', testAutoNoBottomSpace); const testAutoAlsoNoTopSpace = async function () { const {dp, picker} = createDP(input, options); Object.assign(outer.style, { height: '360px', overflow: 'auto', }); pushToBottom(outer); scrollToElem(outer, true); outer.scrollTo(0, 150); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); Object.assign(outer.style, { position: 'fixed', bottom: '0', left: '0', width: '300px', marginTop: '', }); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.destroy(); resetPositioning(); return Promise.resolve(); }; it('"auto" makes the picker show on bottom if the visible space both above and below the input < picker height', testAutoAlsoNoTopSpace); const testAutoNoRighSpace = async function () { const {dp, picker} = createDP(input, options); outer.style.marginLeft = 'calc(100vw - 220px)'; scrollToElem(outer, true); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); outer.style.marginLeft = ''; Object.assign(outer.style, { position: 'fixed', top: '0', right: '-100px', width: '300px', }); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.destroy(); resetPositioning(); return Promise.resolve(); }; it('"auto" makes the picker show on right if the visible space on the right of the input < picker width', testAutoNoRighSpace); const testAutoNoLeftSpaneWhenRtl = async function () { const {dp, picker} = createDP(input, options); outer.style.width = '200px'; wrapper.style.direction = 'rtl'; scrollToElem(outer); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); outer.style.marginLeft = ''; Object.assign(outer.style, { position: 'fixed', top: '0', left: '0', }); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.destroy(); resetPositioning(); return Promise.resolve(); }; it('"auto" makes the picker show on left if the input\'s direction = rtl and the visible space on the left of the input < picker width', testAutoNoLeftSpaneWhenRtl); const testAutoInputExceedsLeftEdge = async function () { const {dp, picker} = createDP(input, options); outer.style.marginLeft = '-40px'; scrollToElem(outer, true); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', 0); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', 0); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); Object.assign(outer.style, { marginLeft: '40px', width: '300px', overflow: 'auto', }); wrapper.style.width = '340px'; outer.scrollTo(40, 0); dp.show(); pickerRect = picker.getBoundingClientRect(); expect(pickerRect.left, 'to be', 40); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.hide(); Object.assign(outer.style, { position: 'fixed', top: '0', left: '30px', marginLeft: '', }); scrollToElem(outer); outer.scrollTo(40, 0); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', 30); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); outer.style.left = '-30px'; outer.scrollTo(0, 0); dp.show(); pickerRect = picker.getBoundingClientRect(); expect(pickerRect.left, 'to be', 0); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.destroy(); resetPositioning(); return Promise.resolve(); }; it('"auto" makes the picker move to the left edge of visible area if picker\'s left < visible area\'s', testAutoInputExceedsLeftEdge); const testAutoInputExceedsRightEdge = async function () { const {dp, picker} = createDP(input, options); outer.style.marginLeft = 'calc(100vw - 100px)'; scrollToElem(outer, true); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', docElem.clientWidth - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', docElem.clientWidth - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); Object.assign(outer.style, { marginLeft: 'calc(100vw - 340px)', width: '300px', overflow: 'auto', }); wrapper.style.marginLeft = '130px'; dp.show(); let outerRect; ([pickerRect, outerRect] = await getRectsOf(picker, outer)); expect(pickerRect.left, 'to be', outerRect.right - pickerRect.width); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.hide(); Object.assign(outer.style, { position: 'fixed', top: '0', right: '40px', }); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', docElem.clientWidth - 40 - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); wrapper.style.marginLeft = ''; outer.style.right = '-150px'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', docElem.clientWidth - pickerRect.width); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.destroy(); resetPositioning(); return Promise.resolve(); }; it('"auto" makes the picker move to the right edge of visible area if picker\'s right < visible area\'s', testAutoInputExceedsRightEdge); it('"top" makes the picker show on top of the input regardless of the size of the space above', async function () { let {dp, picker} = createDP(input, {orientation: 'top'}); // bottom-left when auto scrollToElem(outer); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.hide(); // bottom-right when auto wrapper.setAttribute('dir', 'rtl'); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.destroy(); resetPositioning(); // top-left when auto // bottom space of window < picker height ({dp, picker} = createDP(input, {orientation: 'top'})); wrapper.style.paddingBottom = ''; pushToBottom(outer); scrollToElem(outer, true); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.destroy(); resetPositioning(); // bottom-left when auto // both top/bottom spaces of scroll parent < picker height ({dp, picker} = createDP(input, {orientation: 'top'})); Object.assign(outer.style, { height: '360px', overflow: 'auto', }); pushToBottom(outer); scrollToElem(outer); outer.scrollTo(0, 150); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.hide(); // both top/bottom spaces of scroll parent (relative) < picker height outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.hide(); // both top/bottom spaces of scroll parent (fixed) < picker height Object.assign(outer.style, { position: 'fixed', top: '0', left: '0', width: '300px', marginTop: '', }); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.destroy(); return Promise.resolve(); }); it('"bottom" makes the picker show on bottom of the input regardless of the size of the space above', async function () { let {dp, picker} = createDP(input, {orientation: 'bottom'}); // bottom-left when auto scrollToElem(outer); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.destroy(); // top-left when auto // bottom space of window < picker height ({dp, picker} = createDP(input, {orientation: 'bottom'})); wrapper.style.paddingBottom = ''; pushToBottom(outer); scrollToElem(outer, true); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); // top-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.destroy(); resetPositioning(); // top-left when auto // bottom space of scroll parent < picker height ({dp, picker} = createDP(input, {orientation: 'bottom'})); Object.assign(outer.style, { height: '360px', overflow: 'auto', }); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); // top-left when auto // bottom space of scroll parent (relative) < picker height outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); // bottom spave of scroll parent (fixed) < picker height Object.assign(outer.style, { position: 'fixed', left: '0', bottom: '300px', height: '360px', width: '300px', overflow: 'auto', marginTop: '', }); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.destroy(); return Promise.resolve(); }); it('"left" makes the picker show on left of the input regardless of the text direction or picker\'s right edge position', async function () { let {dp, picker} = createDP(input, {orientation: 'left'}); // bottom-left when auto scrollToElem(outer); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); // bottom-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.destroy(); resetPositioning(); // top-right when auto // bottom space of window < picker height ({dp, picker} = createDP(input, {orientation: 'left'})); wrapper.setAttribute('dir', 'rtl'); wrapper.style.paddingBottom = ''; pushToBottom(outer); scrollToElem(outer, true); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.destroy(); resetPositioning(); // bottom-right when auto // right space of window < picker's right - input's right ({dp, picker} = createDP(input, {orientation: 'left'})); Object.assign(outer.style, { marginTop: '', marginLeft: 'calc(100vw - 200px)', }); wrapper.style.paddingBottom = '320px'; scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.hide(); // right space of window < picker's right - input's right outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.hide(); // right space of window < picker's right - input's right outer.style.marginLeft = ''; Object.assign(outer.style, { position: 'fixed', top: '0', right: '-100px', width: '300px', }); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.hide(); outer.removeAttribute('style'); outer.style.marginLeft = 'calc(100vw - 100px)'; scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.hide(); // input's right > right side of window outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.hide(); // input's right > right side of scroll parent (relative) Object.assign(outer.style, { marginLeft: 'calc(100vw - 340px)', width: '300px', overflow: 'auto', }); wrapper.style.marginLeft = '150px'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.hide(); // input's right > right side of scroll parent (fixed) Object.assign(outer.style, { position: 'fixed', top: '0', right: '40px', }); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.hide(); // input's right > right side of window wrapper.style.marginLeft = ''; outer.style.right = '-150px'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); dp.destroy(); return Promise.resolve(); }); it('"right" makes the picker show on right of the input regardless of the text direction or picker\'s left edge position', async function () { let {dp, picker} = createDP(input, {orientation: 'right'}); // bottom-left when auto scrollToElem(outer); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); // bottom-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.destroy(); resetPositioning(); // top-left when auto // bottom space of window < picker height ({dp, picker} = createDP(input, {orientation: 'right'})); wrapper.style.paddingBottom = ''; pushToBottom(outer); scrollToElem(outer, true); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-top'), 'to be true'); dp.destroy(); resetPositioning(); // bottom-left when auto // left space of window < picker's left - input's left (direcion = rtl) ({dp, picker} = createDP(input, {orientation: 'right'})); outer.style.width = '200px'; wrapper.style.direction = 'rtl'; scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.hide(); // left space of window < picker's left - input's left (direcion = rtl) outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.hide(); // left space of window < picker's left - input's left (direcion = rtl) outer.style.marginLeft = ''; Object.assign(outer.style, { position: 'fixed', top: '0', left: '0', }); scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.hide(); // input's left < left side of window (direcion = rtl) outer.removeAttribute('style'); outer.style.marginLeft = '-40px'; scrollToElem(outer); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.hide(); // input's left < left side of window (direcion = rtl) outer.style.position = 'relative'; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.hide(); // left space of scroll parent (relative) < picker's left - input's left (direcion = rtl) Object.assign(outer.style, { marginLeft: '40px', width: '300px', overflow: 'auto', }); wrapper.style.width = '340px'; outer.scrollTo(40, 0); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.hide(); // left space of scroll parent (fixed) < picker's left - input's left (direcion = rtl) Object.assign(outer.style, { position: 'fixed', top: '0', left: '30px', marginLeft: '', }); scrollToElem(outer); outer.scrollTo(40, 0); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.hide(); // input's left < left side of window (direcion = rtl) Object.assign(outer.style, { left: '-40px', width: '200px', overflow: '', }); wrapper.style.width = ''; dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); dp.destroy(); return Promise.resolve(); }); it('"top left" makes the picker always show on top left of the input', async function () { let {dp, picker} = createDP(input, {orientation: 'top left'}); // bottom-left when auto wrapper.style.marginTop = '300px'; dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); dp.hide(); // bottom-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); dp.destroy(); resetPositioning(); // top-left when auto ({dp, picker} = createDP(input, {orientation: 'top left'})); wrapper.style.paddingBottom = ''; pushToBottom(outer); scrollToElem(outer, true); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); dp.hide(); // top-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); dp.destroy(); return Promise.resolve(); }); it('"top right" makes the picker always show on top right of the input', async function () { const {dp, picker} = createDP(input, {orientation: 'top right'}); // bottom-left when auto scrollToElem(outer); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); dp.hide(); // bottom-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); dp.hide(); // top-left when auto wrapper.style.paddingBottom = ''; pushToBottom(outer); scrollToElem(outer, true); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); dp.hide(); // top-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.top - pickerRect.height); dp.destroy(); return Promise.resolve(); }); it('"bottom left" makes the picker always show on bottom left of the input', async function () { let {dp, picker} = createDP(input, {orientation: 'bottom left'}); // bottom-left when auto scrollToElem(outer); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); dp.hide(); // bottom-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); dp.destroy(); resetPositioning(); // top-left when auto ({dp, picker} = createDP(input, {orientation: 'bottom left'})); wrapper.style.paddingBottom = ''; pushToBottom(outer); scrollToElem(outer, true); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); dp.hide(); // top-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); dp.destroy(); return Promise.resolve(); }); it('"bottom right" makes the picker always show on bottom right of the input', async function () { let {dp, picker} = createDP(input, {orientation: 'bottom right'}); // top-left when auto scrollToElem(outer); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); dp.hide(); // bottom-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); dp.destroy(); resetPositioning(); // top-left when auto ({dp, picker} = createDP(input, {orientation: 'bottom right'})); wrapper.style.paddingBottom = ''; pushToBottom(outer); scrollToElem(outer, true); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); dp.hide(); // top-right when auto wrapper.setAttribute('dir', 'rtl'); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); dp.destroy(); return Promise.resolve(); }); it('can be updated with setOptions()', async function () { const {dp, picker} = createDP(input); scrollToElem(outer); dp.setOptions({orientation: 'right bottom'}); dp.show(); let [pickerRect, inputRect] = await getRectsOf(picker, input); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); dp.setOptions({orientation: 'bottom auto'}); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); dp.setOptions({orientation: 'auto right'}); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.right - pickerRect.width); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-right'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); dp.hide(); dp.setOptions({orientation: 'auto'}); dp.show(); ([pickerRect, inputRect] = await getRectsOf(picker, input)); expect(pickerRect.left, 'to be', inputRect.left); expect(pickerRect.top, 'to be', inputRect.bottom); expect(picker.classList.contains('datepicker-orient-left'), 'to be true'); expect(picker.classList.contains('datepicker-orient-bottom'), 'to be true'); return Promise.resolve(); }); const doCommonTests = async (container = undefined) => { options = container ? {container} : {}; await testAutoDefault(); await testAutoRtl(); await testAutoNoBottomSpace(); await testAutoAlsoNoTopSpace(); await testAutoNoRighSpace(); await testAutoNoLeftSpaneWhenRtl(); await testAutoInputExceedsLeftEdge(); await testAutoInputExceedsRightEdge(); return Promise.resolve(); }; describe('with container option', function () { it('works the same when input and container are in the same scrolling area', function () { return doCommonTests(wrapper); }); it('works the same when container is the scroll parent of input', function () { return doCommonTests('#outer'); }); }); describe('with custom element', function () { const prepareCustomWrapper = (useSlot = false) => { const testWrapper = document.createElement('test-wrapper'); testWrapper.id = wrapper.id; testWrapper.setAttribute('style', wrapper.getAttribute('style')); (useSlot ? testWrapper : testWrapper.shadowRoot).appendChild(input); outer.replaceChild(testWrapper, wrapper); wrapper = testWrapper; }; const prepareCustomOuter = (useSlot = false) => { const testWrapper = document.createElement('test-wrapper'); testWrapper.id = outer.id; (useSlot ? testWrapper : testWrapper.shadowRoot).appendChild(wrapper); testContainer.replaceChild(testWrapper, outer); outer = testWrapper; }; describe('and default container', function () { it('works the same when input is inside custom element\'s shadow DOM', function () { prepareCustomWrapper(); return doCommonTests(); }); it('works the same when input is slotted into custom element', function () { prepareCustomWrapper(true); return doCommonTests(); }); it('works the same when custom element is scroll parent and input is inside its shadow DOM', function () { prepareCustomOuter(); return doCommonTests(); }); it('works the same when custom element is scroll parent and input is inside its slot', function () { prepareCustomOuter(true); return doCommonTests(); }); }); describe('and container inside the scrolling area', function () { it('works the same when input is inside custom element\'s shadow DOM', function () { prepareCustomWrapper(); return doCommonTests(wrapper); }); it('works the same when input is slotted into custom element', function () { prepareCustomWrapper(true); return doCommonTests(wrapper); }); it('works the same when custom element is scroll parent and input is inside its shadow DOM', function () { prepareCustomOuter(); return doCommonTests(wrapper); }); it('works the same when custom element is scroll parent and input is inside its slot', function () { prepareCustomOuter(true); return doCommonTests(wrapper); }); }); describe('and container that is also scroll parent', function () { it('works the same when input is inside custom element\'s shadow DOM', function () { prepareCustomWrapper(); return doCommonTests(outer); }); it('works the same when input is slotted into custom element', function () { prepareCustomWrapper(true); return doCommonTests(outer); }); it('works the same when custom element is scroll parent and input is inside its shadow DOM', function () { prepareCustomOuter(); return doCommonTests(outer); }); it('works the same when custom element is scroll parent and input is inside its slot', function () { prepareCustomOuter(true); return doCommonTests(outer); }); }); }); });