added mouseleave timeout with JS
This commit is contained in:
parent
f20a48494a
commit
20bfc4f679
|
@ -1,3 +1,16 @@
|
|||
let hideTimeoutInterval = 0;
|
||||
let hideTimeout;
|
||||
let subitems;
|
||||
|
||||
export const getHideTimeoutInterval = () => hideTimeoutInterval;
|
||||
|
||||
export const hideAllSubItems = () => {
|
||||
subitems.forEach((el) => {
|
||||
el.parentNode.classList.remove('is-over');
|
||||
el.style.display = 'none'; // eslint-disable-line no-param-reassign
|
||||
});
|
||||
};
|
||||
|
||||
export const calculateTop = (boundingRect, outerHeight) => {
|
||||
const windowHeight = window.innerHeight;
|
||||
const bottomOverflow = windowHeight - (boundingRect.top + outerHeight);
|
||||
|
@ -6,23 +19,64 @@ export const calculateTop = (boundingRect, outerHeight) => {
|
|||
boundingRect.top;
|
||||
};
|
||||
|
||||
export default () => {
|
||||
$('.sidebar-top-level-items > li:not(.active)').on('mouseover', (e) => {
|
||||
const $this = e.currentTarget;
|
||||
const $subitems = $('.sidebar-sub-level-items', $this).show();
|
||||
export const showSubLevelItems = (el) => {
|
||||
const $subitems = el.querySelector('.sidebar-sub-level-items');
|
||||
|
||||
if ($subitems.length) {
|
||||
const boundingRect = $this.getBoundingClientRect();
|
||||
const top = calculateTop(boundingRect, $subitems.outerHeight());
|
||||
const isAbove = top < boundingRect.top;
|
||||
if (!$subitems) return;
|
||||
|
||||
$subitems.css({
|
||||
transform: `translate3d(0, ${top}px, 0)`,
|
||||
});
|
||||
hideAllSubItems();
|
||||
|
||||
if (isAbove) {
|
||||
$subitems.addClass('is-above');
|
||||
}
|
||||
}
|
||||
}).on('mouseout', e => $('.sidebar-sub-level-items', e.currentTarget).hide().removeClass('is-above'));
|
||||
if (el.classList.contains('is-over')) {
|
||||
clearTimeout(hideTimeout);
|
||||
} else {
|
||||
$subitems.style.display = 'block';
|
||||
el.classList.add('is-over');
|
||||
}
|
||||
|
||||
const boundingRect = el.getBoundingClientRect();
|
||||
const top = calculateTop(boundingRect, $subitems.offsetHeight);
|
||||
const isAbove = top < boundingRect.top;
|
||||
|
||||
$subitems.style.transform = `translate3d(0, ${top}px, 0)`;
|
||||
|
||||
if (isAbove) {
|
||||
$subitems.classList.add('is-above');
|
||||
}
|
||||
};
|
||||
|
||||
export const hideSubLevelItems = (el) => {
|
||||
const $subitems = el.querySelector('.sidebar-sub-level-items');
|
||||
const hideFn = () => {
|
||||
el.classList.remove('is-over');
|
||||
$subitems.style.display = 'none';
|
||||
$subitems.classList.remove('is-above');
|
||||
|
||||
hideTimeoutInterval = 0;
|
||||
};
|
||||
|
||||
if ($subitems && hideTimeoutInterval) {
|
||||
hideTimeout = setTimeout(hideFn, hideTimeoutInterval);
|
||||
} else if ($subitems) {
|
||||
hideFn();
|
||||
}
|
||||
};
|
||||
|
||||
export const setMouseOutTimeout = (el) => {
|
||||
if (el.closest('.sidebar-sub-level-items')) {
|
||||
hideTimeoutInterval = 250;
|
||||
} else {
|
||||
hideTimeoutInterval = 0;
|
||||
}
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const items = [...document.querySelectorAll('.sidebar-top-level-items > li:not(.active)')];
|
||||
subitems = [...document.querySelectorAll('.sidebar-top-level-items > li:not(.active) .sidebar-sub-level-items')];
|
||||
|
||||
items.forEach((el) => {
|
||||
el.addEventListener('mouseenter', e => showSubLevelItems(e.currentTarget));
|
||||
el.addEventListener('mouseleave', e => hideSubLevelItems(e.currentTarget));
|
||||
});
|
||||
|
||||
subitems.forEach(el => el.addEventListener('mouseleave', e => setMouseOutTimeout(e.target)));
|
||||
};
|
||||
|
|
|
@ -292,7 +292,8 @@ $new-sidebar-width: 220px;
|
|||
}
|
||||
|
||||
&:not(.active):hover > a,
|
||||
> a:hover {
|
||||
> a:hover,
|
||||
&.is-over > a {
|
||||
background-color: $white-light;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
import { calculateTop } from '~/fly_out_nav';
|
||||
import {
|
||||
calculateTop,
|
||||
setMouseOutTimeout,
|
||||
getHideTimeoutInterval,
|
||||
hideSubLevelItems,
|
||||
showSubLevelItems,
|
||||
} from '~/fly_out_nav';
|
||||
|
||||
describe('Fly out sidebar navigation', () => {
|
||||
let el;
|
||||
beforeEach(() => {
|
||||
el = document.createElement('div');
|
||||
document.body.appendChild(el);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
describe('calculateTop', () => {
|
||||
it('returns boundingRect top', () => {
|
||||
const boundingRect = {
|
||||
|
@ -24,4 +40,119 @@ describe('Fly out sidebar navigation', () => {
|
|||
).toBe(window.innerHeight - 50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setMouseOutTimeout', () => {
|
||||
it('sets hideTimeoutInterval to 150 when inside sub items', () => {
|
||||
el.innerHTML = '<div class="sidebar-sub-level-items"><div class="js-test"></div></div>';
|
||||
|
||||
setMouseOutTimeout(el.querySelector('.js-test'));
|
||||
|
||||
expect(
|
||||
getHideTimeoutInterval(),
|
||||
).toBe(150);
|
||||
});
|
||||
|
||||
it('resets hideTimeoutInterval when not inside sub items', () => {
|
||||
setMouseOutTimeout(el);
|
||||
|
||||
expect(
|
||||
getHideTimeoutInterval(),
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideSubLevelItems', () => {
|
||||
beforeEach(() => {
|
||||
el.innerHTML = '<div class="sidebar-sub-level-items"></div>';
|
||||
});
|
||||
|
||||
it('hides subitems', () => {
|
||||
hideSubLevelItems(el);
|
||||
|
||||
expect(
|
||||
el.querySelector('.sidebar-sub-level-items').style.display,
|
||||
).toBe('none');
|
||||
});
|
||||
|
||||
it('removes is-over class', () => {
|
||||
spyOn(el.classList, 'remove');
|
||||
|
||||
hideSubLevelItems(el);
|
||||
|
||||
expect(
|
||||
el.classList.remove,
|
||||
).toHaveBeenCalledWith('is-over');
|
||||
});
|
||||
|
||||
it('removes is-above class from sub-items', () => {
|
||||
const subItems = el.querySelector('.sidebar-sub-level-items');
|
||||
|
||||
spyOn(subItems.classList, 'remove');
|
||||
|
||||
hideSubLevelItems(el);
|
||||
|
||||
expect(
|
||||
subItems.classList.remove,
|
||||
).toHaveBeenCalledWith('is-above');
|
||||
});
|
||||
|
||||
it('does nothing if el has no sub-items', () => {
|
||||
el.innerHTML = '';
|
||||
|
||||
spyOn(el.classList, 'remove');
|
||||
|
||||
hideSubLevelItems(el);
|
||||
|
||||
expect(
|
||||
el.classList.remove,
|
||||
).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('showSubLevelItems', () => {
|
||||
beforeEach(() => {
|
||||
el.innerHTML = '<div class="sidebar-sub-level-items"></div>';
|
||||
});
|
||||
|
||||
it('adds is-over class to el', () => {
|
||||
spyOn(el.classList, 'add');
|
||||
|
||||
showSubLevelItems(el);
|
||||
|
||||
expect(
|
||||
el.classList.add,
|
||||
).toHaveBeenCalledWith('is-over');
|
||||
});
|
||||
|
||||
it('shows sub-items', () => {
|
||||
showSubLevelItems(el);
|
||||
|
||||
expect(
|
||||
el.querySelector('.sidebar-sub-level-items').style.display,
|
||||
).toBe('block');
|
||||
});
|
||||
|
||||
it('sets transform of sub-items', () => {
|
||||
showSubLevelItems(el);
|
||||
|
||||
expect(
|
||||
el.querySelector('.sidebar-sub-level-items').style.transform,
|
||||
).toBe(`translate3d(0px, ${el.offsetTop}px, 0px)`);
|
||||
});
|
||||
|
||||
it('sets is-above when element is above', () => {
|
||||
const subItems = el.querySelector('.sidebar-sub-level-items');
|
||||
subItems.style.height = '5000px';
|
||||
el.style.position = 'relative';
|
||||
el.style.top = '1000px';
|
||||
|
||||
spyOn(el.classList, 'add');
|
||||
|
||||
showSubLevelItems(el);
|
||||
|
||||
expect(
|
||||
el.classList.add,
|
||||
).toHaveBeenCalledWith('is-above');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue