Skip to content

Commit 36f3a19

Browse files
committed
Lock and unlock disabled targets
1 parent a5b6bbd commit 36f3a19

File tree

5 files changed

+52
-38
lines changed

5 files changed

+52
-38
lines changed

src/components/Filter/FilterInput.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
</button>
3838
<div
3939
:class="['filter__input-box-wrapper', { 'scrolling': isScrolling }]"
40+
v-bind="{[SCROLL_LOCK_DISABLE_ATTR]: true}"
4041
@scroll="handleScroll"
4142
>
4243
<TagList
@@ -106,7 +107,7 @@
106107
</div>
107108
</div>
108109
<TagList
109-
v-if="displaySuggestedTags"
110+
v-show="displaySuggestedTags"
110111
:id="SuggestedTagsId"
111112
ref="suggestedTags"
112113
:ariaLabel="$tc('filter.suggested-tags', suggestedTags.length)"
@@ -128,6 +129,7 @@
128129
import ClearRoundedIcon from 'theme/components/Icons/ClearRoundedIcon.vue';
129130
import multipleSelection from 'docc-render/mixins/multipleSelection';
130131
import handleScrollbar from 'docc-render/mixins/handleScrollbar';
132+
import { SCROLL_LOCK_DISABLE_ATTR } from 'docc-render/utils/scroll-lock';
131133
import FilterIcon from 'theme/components/Icons/FilterIcon.vue';
132134
import TagList from './TagList.vue';
133135
@@ -225,6 +227,7 @@ export default {
225227
SuggestedTagsId,
226228
AXinputProperties,
227229
showSuggestedTags: false,
230+
SCROLL_LOCK_DISABLE_ATTR,
228231
};
229232
},
230233
computed: {

src/components/Filter/TagList.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
role="listbox"
2525
:aria-multiselectable="areTagsRemovable ? 'true' : 'false'"
2626
aria-orientation="horizontal"
27+
v-bind="{[SCROLL_LOCK_DISABLE_ATTR]: true}"
2728
@keydown.left.capture.prevent="focusPrev"
2829
@keydown.right.capture.prevent="focusNext"
2930
@keydown.up.capture.prevent="focusPrev"
@@ -61,6 +62,7 @@
6162
import { isSingleCharacter } from 'docc-render/utils/input-helper';
6263
import handleScrollbar from 'docc-render/mixins/handleScrollbar';
6364
import keyboardNavigation from 'docc-render/mixins/keyboardNavigation';
65+
import { SCROLL_LOCK_DISABLE_ATTR } from 'docc-render/utils/scroll-lock';
6466
import Tag from './Tag.vue';
6567
6668
export default {
@@ -69,6 +71,11 @@ export default {
6971
handleScrollbar,
7072
keyboardNavigation,
7173
],
74+
data() {
75+
return {
76+
SCROLL_LOCK_DISABLE_ATTR,
77+
};
78+
},
7279
props: {
7380
tags: {
7481
type: Array,

src/utils/scroll-lock.js

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,24 +73,26 @@ function advancedUnlock(targetElement) {
7373
document.removeEventListener('touchmove', preventDefault);
7474
}
7575

76+
const isVerticalScroll = ({ scrollHeight, scrollWidth }) => scrollHeight > scrollWidth;
77+
7678
/**
7779
* Handles the scrolling of the targetElement
7880
* @param {TouchEvent} event
7981
* @param {HTMLElement} targetElement
8082
* @return {boolean}
8183
*/
82-
function handleScroll(event, targetElement) {
83-
const clientY = event.targetTouches[0].clientY - initialClientY;
84-
// check if any parent has a scroll-lock disable, if not use the targetElement
85-
const target = event.target.closest(`[${SCROLL_LOCK_DISABLE_ATTR}]`) || targetElement;
86-
if (target.scrollTop === 0 && clientY > 0) {
87-
// element is at the top of its scroll.
88-
return preventDefault(event);
89-
}
84+
function handleScroll(event, target) {
85+
if (isVerticalScroll(target)) {
86+
const clientY = event.targetTouches[0].clientY - initialClientY;
87+
if (target.scrollTop === 0 && clientY > 0) {
88+
// element is at the top of its scroll.
89+
return preventDefault(event);
90+
}
9091

91-
if (isTargetElementTotallyScrolled(target) && clientY < 0) {
92-
// element is at the bottom of its scroll.
93-
return preventDefault(event);
92+
if (isTargetElementTotallyScrolled(target) && clientY < 0) {
93+
// element is at the bottom of its scroll.
94+
return preventDefault(event);
95+
}
9496
}
9597

9698
// prevent the scroll event from going up to the parent/window
@@ -138,7 +140,11 @@ export default {
138140
if (!isIosDevice()) {
139141
simpleLock();
140142
} else {
143+
// lock everything but target element
141144
advancedLock(targetElement);
145+
// lock everything but disabled targets
146+
const disabledTargets = document.querySelectorAll(`[${SCROLL_LOCK_DISABLE_ATTR}]`);
147+
disabledTargets.forEach(target => advancedLock(target));
142148
}
143149
isLocked = true;
144150
},
@@ -152,6 +158,9 @@ export default {
152158
if (isIosDevice()) {
153159
// revert the old scroll position
154160
advancedUnlock(targetElement);
161+
// revert the old scroll position for disabled targets
162+
const disabledTargets = document.querySelectorAll(`[${SCROLL_LOCK_DISABLE_ATTR}]`);
163+
disabledTargets.forEach(target => advancedUnlock(target));
155164
} else {
156165
// remove all inline styles added by the `simpleLock` function
157166
document.body.style.removeProperty('overflow');

tests/unit/components/Filter/FilterInput.spec.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ describe('FilterInput', () => {
378378
});
379379
expect(selectedTags.props('activeTags')).toEqual(tags);
380380
// assert we tried to ficus first item
381-
expect(spy).toHaveBeenCalledWith(0);
381+
expect(spy).not.toHaveBeenCalled();
382382
});
383383

384384
it('on paste, handles clipboard in HTML format, when copying and pasting from search directly', () => {
@@ -563,7 +563,7 @@ describe('FilterInput', () => {
563563
await wrapper.vm.$nextTick();
564564
// first time was `true`, from `focus`, then `blur` made it `false`
565565
expect(wrapper.emitted('show-suggested-tags')).toEqual([[true], [false]]);
566-
expect(suggestedTags.exists()).toBe(false);
566+
expect(suggestedTags.attributes('style')).toContain('display: none');
567567
});
568568

569569
it('deletes `suggestedTags` component when `deleteButton` looses its focus on an external component', async () => {
@@ -576,7 +576,7 @@ describe('FilterInput', () => {
576576
});
577577
await wrapper.vm.$nextTick();
578578
expect(wrapper.emitted('show-suggested-tags')).toEqual([[true], [false]]);
579-
expect(suggestedTags.exists()).toBe(false);
579+
expect(suggestedTags.attributes('style')).toContain('display: none');
580580
});
581581

582582
it('does not hide the tags, if the new focus target matches `input, button`, inside the main component', async () => {
@@ -598,7 +598,7 @@ describe('FilterInput', () => {
598598

599599
await flushPromises();
600600

601-
expect(suggestedTags.exists()).toBe(false);
601+
expect(suggestedTags.attributes('style')).toContain('display: none');
602602
expect(wrapper.emitted('show-suggested-tags')).toEqual([[true], [false]]);
603603
});
604604

@@ -613,7 +613,7 @@ describe('FilterInput', () => {
613613
});
614614

615615
await wrapper.vm.$nextTick();
616-
expect(suggestedTags.exists()).toBe(false);
616+
expect(suggestedTags.attributes('style')).toContain('display: none');
617617
expect(wrapper.emitted('show-suggested-tags')).toEqual([[true], [false]]);
618618
expect(wrapper.emitted('blur')).toBeTruthy();
619619
});
@@ -637,7 +637,7 @@ describe('FilterInput', () => {
637637
});
638638

639639
it('deletes `suggestedTags` component when no tags available', () => {
640-
expect(suggestedTags.exists()).toBe(false);
640+
expect(suggestedTags.attributes('style')).toContain('display: none');
641641
});
642642

643643
it('does not render `deleteButton` when there are no tags and `input` is empty', () => {

tests/unit/utils/scroll-lock.spec.js

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ describe('scroll-lock', () => {
3131
DOM = parseHTMLString(`
3232
<div class="container">
3333
<div class="scrollable">long</div>
34+
<div ${SCROLL_LOCK_DISABLE_ATTR}="true" class="disabled-target"></div>
3435
</div>
3536
`);
3637
document.body.appendChild(DOM);
3738
container = DOM.querySelector('.container');
39+
Object.defineProperty(container, 'scrollHeight', { value: 10, writable: true });
3840
});
3941
afterEach(() => {
4042
window.navigator.platform = platform;
@@ -70,12 +72,7 @@ describe('scroll-lock', () => {
7072
container.ontouchmove(touchMoveEvent);
7173
expect(preventDefault).toHaveBeenCalledTimes(1);
7274
expect(stopPropagation).toHaveBeenCalledTimes(0);
73-
expect(touchMoveEvent.target.closest).toHaveBeenCalledTimes(1);
74-
expect(touchMoveEvent.target.closest).toHaveBeenCalledWith(`[${SCROLL_LOCK_DISABLE_ATTR}]`);
75-
7675
// simulate scroll middle
77-
// simulate we have enough to scroll
78-
Object.defineProperty(container, 'scrollHeight', { value: 10, writable: true });
7976
container.ontouchmove({ ...touchMoveEvent, targetTouches: [{ clientY: -10 }] });
8077
expect(preventDefault).toHaveBeenCalledTimes(1);
8178
expect(stopPropagation).toHaveBeenCalledTimes(1);
@@ -86,26 +83,24 @@ describe('scroll-lock', () => {
8683
expect(preventDefault).toHaveBeenCalledTimes(2);
8784
expect(stopPropagation).toHaveBeenCalledTimes(1);
8885

89-
// simulate there is a scroll-lock-disable target
90-
container.ontouchmove({
91-
...touchMoveEvent,
92-
targetTouches: [{ clientY: -10 }],
93-
target: {
94-
closest: jest.fn().mockReturnValue({
95-
...container,
96-
clientHeight: 150,
97-
}),
98-
},
99-
});
100-
// assert scrolling was allowed
101-
expect(preventDefault).toHaveBeenCalledTimes(2);
102-
expect(stopPropagation).toHaveBeenCalledTimes(2);
103-
10486
scrollLock.unlockScroll(container);
10587
expect(container.ontouchmove).toBeFalsy();
10688
expect(container.ontouchstart).toBeFalsy();
10789
});
10890

91+
it('adds event listeners to the disabled targets too', () => {
92+
const disabledTarget = DOM.querySelector('.disabled-target');
93+
// init the scroll lock
94+
scrollLock.lockScroll(container);
95+
// assert event listeners are attached
96+
expect(disabledTarget.ontouchstart).toEqual(expect.any(Function));
97+
expect(disabledTarget.ontouchmove).toEqual(expect.any(Function));
98+
99+
scrollLock.unlockScroll(container);
100+
expect(disabledTarget.ontouchmove).toBeFalsy();
101+
expect(disabledTarget.ontouchstart).toBeFalsy();
102+
});
103+
109104
it('prevents body scrolling', () => {
110105
scrollLock.lockScroll(container);
111106
// assert body scroll is getting prevented when swiping up/down

0 commit comments

Comments
 (0)