From ca913687365a7048a454c1ef4509285eab468764 Mon Sep 17 00:00:00 2001 From: zzz1ck <1412629+zzz1ck@users.noreply.github.com> Date: Mon, 21 Oct 2024 17:41:06 +0300 Subject: [PATCH 1/2] Fix: UI jumps when open calendar then scroll from the view and click outside --- package.json | 2 +- src/components/Datepicker.vue | 2 ++ src/utils/IsElementInViewport.js | 9 +++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/utils/IsElementInViewport.js diff --git a/package.json b/package.json index d5bd30b1..49fcaa1e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@reedsy/vuejs-datepicker", - "version": "1.6.2-reedsy-2.1.8", + "version": "1.6.2-reedsy-2.1.9", "description": "A simple Vue.js datepicker component. Supports disabling of dates, inline mode, translations", "keywords": [ "vue", diff --git a/src/components/Datepicker.vue b/src/components/Datepicker.vue index c285394e..2826c0e5 100644 --- a/src/components/Datepicker.vue +++ b/src/components/Datepicker.vue @@ -168,6 +168,7 @@ import PickerYear from './PickerYear.vue'; import utils, { makeDateUtils } from '../utils/DateUtils'; import { ELEMENT_IDS } from '../config/ElementIds'; import { getFocusableChildren } from '../utils/FocusableElements'; +import { isElementInViewport } from '../utils/IsElementInViewport'; export default { name: 'DatePicker', components: { @@ -603,6 +604,7 @@ export default { if (!input) return; const inputEl = input.$el.querySelector('input'); if (!inputEl) return; + if (!isElementInViewport(inputEl)) return; inputEl.focus(); } diff --git a/src/utils/IsElementInViewport.js b/src/utils/IsElementInViewport.js new file mode 100644 index 00000000..513bf451 --- /dev/null +++ b/src/utils/IsElementInViewport.js @@ -0,0 +1,9 @@ +export function isElementInViewport(el) { + const rect = el.getBoundingClientRect(); + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); +} From 6fc65c52ea0d99f6094c0aaeef87e265099f788d Mon Sep 17 00:00:00 2001 From: zzz1ck <1412629+zzz1ck@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:03:49 +0300 Subject: [PATCH 2/2] add tests --- test/unit/specs/Datepicker/Datepicker.spec.js | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/unit/specs/Datepicker/Datepicker.spec.js b/test/unit/specs/Datepicker/Datepicker.spec.js index 77259e64..00f467f3 100644 --- a/test/unit/specs/Datepicker/Datepicker.spec.js +++ b/test/unit/specs/Datepicker/Datepicker.spec.js @@ -432,6 +432,44 @@ describe('Focus after closing the datepicker', () => { const input = wrapper.vm.$refs.input.$el.querySelector('input'); expect(document.activeElement).toEqual(input); }); + describe('after scrolling and clicking outside', () => { + let wrapper; + let originalGetBoundingClientRect; + beforeEach(() => { + wrapper = mount(Datepicker, { attachTo: document.body }); + originalGetBoundingClientRect = Element.prototype.getBoundingClientRect; + }); + afterEach(() => { + Element.prototype.getBoundingClientRect = originalGetBoundingClientRect; + wrapper.unmount(); + }); + + it('should focus on the input when it is in viewport', async () => { + Element.prototype.getBoundingClientRect = jest.fn(() => ({ + top: 100, + bottom: 0, + left: 0, + right: 0, + })); + await wrapper.vm.showCalendar(); + wrapper.vm.clickOutside({ target: document.body }); + const input = wrapper.vm.$refs.input.$el.querySelector('input'); + expect(document.activeElement).toEqual(input); + }); + + it('should not focus on the input when it is out of viewport', async () => { + Element.prototype.getBoundingClientRect = jest.fn(() => ({ + top: -100, + bottom: 0, + left: 0, + right: 0, + })); + await wrapper.vm.showCalendar(); + wrapper.vm.clickOutside({ target: document.body }); + const input = wrapper.vm.$refs.input.$el.querySelector('input'); + expect(document.activeElement).not.toEqual(input); + }); + }); }); describe('Modal', () => {