Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
5a12fa15
Commit
5a12fa15
authored
Aug 18, 2017
by
Phil Hughes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixes the stable branches fly out nav JS
parent
f34faaba
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
47 additions
and
254 deletions
+47
-254
app/assets/javascripts/fly_out_nav.js
app/assets/javascripts/fly_out_nav.js
+19
-126
spec/javascripts/fly_out_nav_spec.js
spec/javascripts/fly_out_nav_spec.js
+28
-128
No files found.
app/assets/javascripts/fly_out_nav.js
View file @
5a12fa15
import
Cookies
from
'
js-cookie
'
;
import
bp
from
'
./breakpoints
'
;
const
HIDE_INTERVAL_TIMEOUT
=
300
;
const
IS_OVER_CLASS
=
'
is-over
'
;
const
IS_ABOVE_CLASS
=
'
is-above
'
;
const
IS_SHOWING_FLY_OUT_CLASS
=
'
is-showing-fly-out
'
;
let
currentOpenMenu
=
null
;
let
menuCornerLocs
;
let
timeoutId
;
export
const
mousePos
=
[];
export
const
setOpenMenu
=
(
menu
=
null
)
=>
{
currentOpenMenu
=
menu
;
};
export
const
slope
=
(
a
,
b
)
=>
(
b
.
y
-
a
.
y
)
/
(
b
.
x
-
a
.
x
);
let
headerHeight
=
50
;
export
const
getHeaderHeight
=
()
=>
headerHeight
;
...
...
@@ -28,28 +14,8 @@ export const canShowActiveSubItems = (el) => {
return
true
;
};
export
const
canShowSubItems
=
()
=>
bp
.
getBreakpointSize
()
===
'
sm
'
||
bp
.
getBreakpointSize
()
===
'
md
'
||
bp
.
getBreakpointSize
()
===
'
lg
'
;
export
const
getHideSubItemsInterval
=
()
=>
{
if
(
!
currentOpenMenu
)
return
0
;
const
currentMousePos
=
mousePos
[
mousePos
.
length
-
1
];
const
prevMousePos
=
mousePos
[
0
];
const
currentMousePosY
=
currentMousePos
.
y
;
const
[
menuTop
,
menuBottom
]
=
menuCornerLocs
;
if
(
currentMousePosY
<
menuTop
.
y
||
currentMousePosY
>
menuBottom
.
y
)
return
0
;
if
(
slope
(
prevMousePos
,
menuBottom
)
<
slope
(
currentMousePos
,
menuBottom
)
&&
slope
(
prevMousePos
,
menuTop
)
>
slope
(
currentMousePos
,
menuTop
))
{
return
HIDE_INTERVAL_TIMEOUT
;
}
return
0
;
};
export
const
calculateTop
=
(
boundingRect
,
outerHeight
)
=>
{
const
windowHeight
=
window
.
innerHeight
;
const
bottomOverflow
=
windowHeight
-
(
boundingRect
.
top
+
outerHeight
);
...
...
@@ -58,120 +24,47 @@ export const calculateTop = (boundingRect, outerHeight) => {
boundingRect
.
top
;
};
export
const
hideMenu
=
(
el
)
=>
{
if
(
!
el
)
return
;
const
parentEl
=
el
.
parentNode
;
export
const
showSubLevelItems
=
(
el
)
=>
{
const
subItems
=
el
.
querySelector
(
'
.sidebar-sub-level-items
'
);
el
.
style
.
display
=
''
;
// eslint-disable-line no-param-reassign
el
.
style
.
transform
=
''
;
// eslint-disable-line no-param-reassign
el
.
classList
.
remove
(
IS_ABOVE_CLASS
);
parentEl
.
classList
.
remove
(
IS_OVER_CLASS
);
parentEl
.
classList
.
remove
(
IS_SHOWING_FLY_OUT_CLASS
);
if
(
!
subItems
||
!
canShowSubItems
()
||
!
canShowActiveSubItems
(
el
))
return
;
setOpenMenu
();
};
subItems
.
style
.
display
=
'
block
'
;
el
.
classList
.
add
(
'
is-showing-fly-out
'
);
el
.
classList
.
add
(
'
is-over
'
);
export
const
moveSubItemsToPosition
=
(
el
,
subItems
)
=>
{
const
boundingRect
=
el
.
getBoundingClientRect
();
const
top
=
calculateTop
(
boundingRect
,
subItems
.
offsetHeight
);
const
isAbove
=
top
<
boundingRect
.
top
;
subItems
.
classList
.
add
(
'
fly-out-list
'
);
subItems
.
style
.
transform
=
`translate3d(0,
${
Math
.
floor
(
top
)
-
headerHeight
}
px, 0)`
;
// eslint-disable-line no-param-reassign
const
subItemsRect
=
subItems
.
getBoundingClientRect
();
menuCornerLocs
=
[
{
x
:
subItemsRect
.
left
,
// left position of the sub items
y
:
subItemsRect
.
top
,
// top position of the sub items
},
{
x
:
subItemsRect
.
left
,
// left position of the sub items
y
:
subItemsRect
.
top
+
subItemsRect
.
height
,
// bottom position of the sub items
},
];
subItems
.
style
.
transform
=
`translate3d(0,
${
Math
.
floor
(
top
)
-
headerHeight
}
px, 0)`
;
if
(
isAbove
)
{
subItems
.
classList
.
add
(
IS_ABOVE_CLASS
);
subItems
.
classList
.
add
(
'
is-above
'
);
}
};
export
const
showSubLevelItems
=
(
el
)
=>
{
const
subItems
=
el
.
querySelector
(
'
.sidebar-sub-level-items
'
);
if
(
!
canShowSubItems
()
||
!
canShowActiveSubItems
(
el
))
return
;
el
.
classList
.
add
(
IS_OVER_CLASS
);
if
(
!
subItems
)
return
;
subItems
.
style
.
display
=
'
block
'
;
el
.
classList
.
add
(
IS_SHOWING_FLY_OUT_CLASS
);
setOpenMenu
(
subItems
);
moveSubItemsToPosition
(
el
,
subItems
);
};
export
const
mouseEnterTopItems
=
(
el
)
=>
{
clearTimeout
(
timeoutId
);
timeoutId
=
setTimeout
(()
=>
{
if
(
currentOpenMenu
)
hideMenu
(
currentOpenMenu
);
showSubLevelItems
(
el
);
},
getHideSubItemsInterval
());
};
export
const
mouseLeaveTopItem
=
(
el
)
=>
{
export
const
hideSubLevelItems
=
(
el
)
=>
{
const
subItems
=
el
.
querySelector
(
'
.sidebar-sub-level-items
'
);
if
(
!
canShowSubItems
()
||
!
canShowActiveSubItems
(
el
)
||
(
subItems
&&
subItems
===
currentOpenMenu
))
return
;
el
.
classList
.
remove
(
IS_OVER_CLASS
);
};
export
const
documentMouseMove
=
(
e
)
=>
{
mousePos
.
push
({
x
:
e
.
clientX
,
y
:
e
.
clientY
,
});
if
(
!
subItems
||
!
canShowSubItems
()
||
!
canShowActiveSubItems
(
el
))
return
;
if
(
mousePos
.
length
>
6
)
mousePos
.
shift
();
el
.
classList
.
remove
(
'
is-showing-fly-out
'
);
el
.
classList
.
remove
(
'
is-over
'
);
subItems
.
style
.
display
=
''
;
subItems
.
style
.
transform
=
''
;
subItems
.
classList
.
remove
(
'
is-above
'
);
};
export
default
()
=>
{
const
sidebar
=
document
.
querySelector
(
'
.sidebar-top-level-items
'
);
if
(
!
sidebar
)
return
;
const
items
=
[...
sidebar
.
querySelectorAll
(
'
.sidebar-top-level-items > li
'
)];
sidebar
.
addEventListener
(
'
mouseleave
'
,
()
=>
{
clearTimeout
(
timeoutId
);
timeoutId
=
setTimeout
(()
=>
{
if
(
currentOpenMenu
)
hideMenu
(
currentOpenMenu
);
},
getHideSubItemsInterval
());
});
const
items
=
[...
document
.
querySelectorAll
(
'
.sidebar-top-level-items > li
'
)]
.
filter
(
el
=>
el
.
querySelector
(
'
.sidebar-sub-level-items
'
));
headerHeight
=
document
.
querySelector
(
'
.nav-sidebar
'
).
offsetTop
;
items
.
forEach
((
el
)
=>
{
const
subItems
=
el
.
querySelector
(
'
.sidebar-sub-level-items
'
);
if
(
subItems
)
{
subItems
.
addEventListener
(
'
mouseleave
'
,
()
=>
{
clearTimeout
(
timeoutId
);
hideMenu
(
currentOpenMenu
);
});
}
el
.
addEventListener
(
'
mouseenter
'
,
e
=>
mouseEnterTopItems
(
e
.
currentTarget
));
el
.
addEventListener
(
'
mouseleave
'
,
e
=>
mouseLeaveTopItem
(
e
.
currentTarget
));
el
.
addEventListener
(
'
mouseenter
'
,
e
=>
showSubLevelItems
(
e
.
currentTarget
));
el
.
addEventListener
(
'
mouseleave
'
,
e
=>
hideSubLevelItems
(
e
.
currentTarget
));
});
document
.
addEventListener
(
'
mousemove
'
,
documentMouseMove
);
};
spec/javascripts/fly_out_nav_spec.js
View file @
5a12fa15
import
Cookies
from
'
js-cookie
'
;
import
{
calculateTop
,
hideSubLevelItems
,
showSubLevelItems
,
canShowSubItems
,
canShowActiveSubItems
,
mouseEnterTopItems
,
mouseLeaveTopItem
,
setOpenMenu
,
mousePos
,
getHideSubItemsInterval
,
documentMouseMove
,
getHeaderHeight
,
}
from
'
~/fly_out_nav
'
;
import
bp
from
'
~/breakpoints
'
;
...
...
@@ -24,14 +19,11 @@ describe('Fly out sidebar navigation', () => {
document
.
body
.
appendChild
(
el
);
spyOn
(
bp
,
'
getBreakpointSize
'
).
and
.
callFake
(()
=>
breakpointSize
);
setOpenMenu
(
null
);
});
afterEach
(()
=>
{
document
.
body
.
innerHTML
=
''
;
el
.
remove
()
;
breakpointSize
=
'
lg
'
;
mousePos
.
length
=
0
;
});
describe
(
'
calculateTop
'
,
()
=>
{
...
...
@@ -58,153 +50,61 @@ describe('Fly out sidebar navigation', () => {
});
});
describe
(
'
getHideSubItemsInterval
'
,
()
=>
{
describe
(
'
hideSubLevelItems
'
,
()
=>
{
beforeEach
(()
=>
{
el
.
innerHTML
=
'
<div class="sidebar-sub-level-items" style="position: fixed; top: 0; left: 100px; height: 150px;"></div>
'
;
});
it
(
'
returns 0 if currentOpenMenu is nil
'
,
()
=>
{
expect
(
getHideSubItemsInterval
(),
).
toBe
(
0
);
el
.
innerHTML
=
'
<div class="sidebar-sub-level-items"></div>
'
;
});
it
(
'
returns 0 when mouse above sub-items
'
,
()
=>
{
showSubLevelItems
(
el
);
documentMouseMove
({
clientX
:
el
.
getBoundingClientRect
().
left
,
clientY
:
el
.
getBoundingClientRect
().
top
,
});
documentMouseMove
({
clientX
:
el
.
getBoundingClientRect
().
left
,
clientY
:
el
.
getBoundingClientRect
().
top
-
50
,
});
it
(
'
hides subitems
'
,
()
=>
{
hideSubLevelItems
(
el
);
expect
(
getHideSubItemsInterval
(),
).
toBe
(
0
);
});
it
(
'
returns 0 when mouse is below sub-items
'
,
()
=>
{
const
subItems
=
el
.
querySelector
(
'
.sidebar-sub-level-items
'
);
showSubLevelItems
(
el
);
documentMouseMove
({
clientX
:
el
.
getBoundingClientRect
().
left
,
clientY
:
el
.
getBoundingClientRect
().
top
,
});
documentMouseMove
({
clientX
:
el
.
getBoundingClientRect
().
left
,
clientY
:
(
el
.
getBoundingClientRect
().
top
-
subItems
.
getBoundingClientRect
().
height
)
+
50
,
el
.
querySelector
(
'
.sidebar-sub-level-items
'
).
style
.
display
,
).
toBe
(
''
);
});
expect
(
getHideSubItemsInterval
(),
).
toBe
(
0
);
});
it
(
'
does not hude subitems on mobile
'
,
()
=>
{
breakpointSize
=
'
xs
'
;
it
(
'
returns 300 when mouse is moved towards sub-items
'
,
()
=>
{
documentMouseMove
({
clientX
:
el
.
getBoundingClientRect
().
left
,
clientY
:
el
.
getBoundingClientRect
().
top
,
});
showSubLevelItems
(
el
);
documentMouseMove
({
clientX
:
el
.
getBoundingClientRect
().
left
+
20
,
clientY
:
el
.
getBoundingClientRect
().
top
+
10
,
});
console
.
log
(
el
);
hideSubLevelItems
(
el
);
expect
(
getHideSubItemsInterval
(),
).
toBe
(
300
);
});
el
.
querySelector
(
'
.sidebar-sub-level-items
'
).
style
.
display
,
).
not
.
toBe
(
'
none
'
);
});
describe
(
'
mouseLeaveTopItem
'
,
()
=>
{
beforeEach
(()
=>
{
it
(
'
removes is-over class
'
,
()
=>
{
spyOn
(
el
.
classList
,
'
remove
'
);
});
it
(
'
removes is-over class if currentOpenMenu is null
'
,
()
=>
{
mouseLeaveTopItem
(
el
);
expect
(
el
.
classList
.
remove
,
).
toHaveBeenCalledWith
(
'
is-over
'
);
});
it
(
'
removes is-over class if currentOpenMenu is null & there are sub-items
'
,
()
=>
{
el
.
innerHTML
=
'
<div class="sidebar-sub-level-items" style="position: absolute;"></div>
'
;
mouseLeaveTopItem
(
el
);
hideSubLevelItems
(
el
);
expect
(
el
.
classList
.
remove
,
).
toHaveBeenCalledWith
(
'
is-over
'
);
});
it
(
'
does not remove is-over class if currentOpenMenu is the passed in sub-items
'
,
()
=>
{
el
.
innerHTML
=
'
<div class="sidebar-sub-level-items" style="position: absolute;"></div>
'
;
setOpenMenu
(
el
.
querySelector
(
'
.sidebar-sub-level-items
'
));
mouseLeaveTopItem
(
el
);
expect
(
el
.
classList
.
remove
,
).
not
.
toHaveBeenCalled
();
});
});
describe
(
'
mouseEnterTopItems
'
,
()
=>
{
beforeEach
(()
=>
{
jasmine
.
clock
().
install
();
el
.
innerHTML
=
'
<div class="sidebar-sub-level-items" style="position: absolute; top: 0; left: 100px; height: 200px;"></div>
'
;
});
afterEach
(()
=>
{
jasmine
.
clock
().
uninstall
();
});
it
(
'
shows sub-items after 0ms if no menu is open
'
,
()
=>
{
mouseEnterTopItems
(
el
);
it
(
'
removes is-above class from sub-items
'
,
()
=>
{
const
subItems
=
el
.
querySelector
(
'
.sidebar-sub-level-items
'
);
expect
(
getHideSubItemsInterval
(),
).
toBe
(
0
);
spyOn
(
subItems
.
classList
,
'
remove
'
);
jasmine
.
clock
().
tick
(
0
);
hideSubLevelItems
(
el
);
expect
(
el
.
querySelector
(
'
.sidebar-sub-level-items
'
).
style
.
display
,
).
toBe
(
'
block
'
);
});
it
(
'
shows sub-items after 300ms if a menu is currently open
'
,
()
=>
{
documentMouseMove
({
clientX
:
el
.
getBoundingClientRect
().
left
,
clientY
:
el
.
getBoundingClientRect
().
top
,
});
setOpenMenu
(
el
.
querySelector
(
'
.sidebar-sub-level-items
'
));
documentMouseMove
({
clientX
:
el
.
getBoundingClientRect
().
left
+
20
,
clientY
:
el
.
getBoundingClientRect
().
top
+
10
,
subItems
.
classList
.
remove
,
).
toHaveBeenCalledWith
(
'
is-above
'
);
});
mouseEnterTopItems
(
el
);
it
(
'
does nothing if el has no sub-items
'
,
()
=>
{
el
.
innerHTML
=
''
;
expect
(
getHideSubItemsInterval
(),
).
toBe
(
300
);
spyOn
(
el
.
classList
,
'
remove
'
);
jasmine
.
clock
().
tick
(
300
);
hideSubLevelItems
(
el
);
expect
(
el
.
querySelector
(
'
.sidebar-sub-level-items
'
).
style
.
display
,
).
toBe
(
'
block
'
);
el
.
classList
.
remove
,
).
not
.
toHaveBeenCalledWith
(
);
});
});
...
...
@@ -233,7 +133,7 @@ describe('Fly out sidebar navigation', () => {
).
not
.
toBe
(
'
block
'
);
});
it
(
'
shows sub-items
'
,
()
=>
{
it
(
'
does not
shows sub-items
'
,
()
=>
{
showSubLevelItems
(
el
);
expect
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment