BigW Consortium Gitlab
Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
gitlab-ce
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Forest Godfrey
gitlab-ce
Commits
237d26b9
Commit
237d26b9
authored
Aug 21, 2017
by
Tim Zallmann
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'fly-out-update-for-stable' into '9-5-stable'
Fixes the stable branches fly out nav JS See merge request !13683
parents
55b15c21
150e8b30
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
68 additions
and
284 deletions
+68
-284
fly_out_nav.js
app/assets/javascripts/fly_out_nav.js
+30
-133
fly_out_nav_spec.js
spec/javascripts/fly_out_nav_spec.js
+38
-151
No files found.
app/assets/javascripts/fly_out_nav.js
View file @
237d26b9
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
;
let
sidebar
;
export
const
setSidebar
=
(
el
)
=>
{
sidebar
=
el
;
};
export
const
getHeaderHeight
=
()
=>
headerHeight
;
export
const
canShowActiveSubItems
=
(
el
)
=>
{
const
isHiddenByMedia
=
bp
.
getBreakpointSize
()
===
'sm'
||
bp
.
getBreakpointSize
()
===
'md'
;
if
(
el
.
classList
.
contains
(
'active'
)
&&
!
isHiddenByMedia
)
{
return
Cookies
.
get
(
'sidebar_collapsed'
)
===
'true'
;
if
(
el
.
classList
.
contains
(
'active'
)
&&
(
sidebar
&&
!
sidebar
.
classList
.
contains
(
'sidebar-icons-only'
)))
{
return
false
;
}
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,51 @@ 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
show
SubLevelItems
=
(
el
)
=>
{
export
const
hide
SubLevelItems
=
(
el
)
=>
{
const
subItems
=
el
.
querySelector
(
'.sidebar-sub-level-items'
);
if
(
!
canShowSubItems
()
||
!
canShowActiveSubItems
(
el
))
return
;
el
.
classList
.
add
(
IS_OVER_CLASS
);
if
(
!
subItems
||
!
canShowSubItems
()
||
!
canShowActiveSubItems
(
el
))
return
;
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
)
=>
{
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
(
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'
)];
const
items
=
[...
document
.
querySelectorAll
(
'.sidebar-top-level-items > li'
)]
.
filter
(
el
=>
el
.
querySelector
(
'.sidebar-sub-level-items'
));
sidebar
.
addEventListener
(
'mouseleave'
,
()
=>
{
clearTimeout
(
timeoutId
);
sidebar
=
document
.
querySelector
(
'.nav-sidebar'
);
timeoutId
=
setTimeout
(()
=>
{
if
(
currentOpenMenu
)
hideMenu
(
currentOpenMenu
);
},
getHideSubItemsInterval
());
});
if
(
sidebar
)
{
headerHeight
=
sidebar
.
offsetTop
;
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
));
});
document
.
addEventListener
(
'mousemove'
,
documentMouseMove
);
items
.
forEach
((
el
)
=>
{
el
.
addEventListener
(
'mouseenter'
,
e
=>
showSubLevelItems
(
e
.
currentTarget
));
el
.
addEventListener
(
'mouseleave'
,
e
=>
hideSubLevelItems
(
e
.
currentTarget
));
});
}
};
spec/javascripts/fly_out_nav_spec.js
View file @
237d26b9
import
Cookies
from
'js-cookie'
;
import
{
calculateTop
,
hideSubLevelItems
,
showSubLevelItems
,
canShowSubItems
,
canShowActiveSubItems
,
mouseEnterTopItems
,
mouseLeaveTopItem
,
setOpenMenu
,
mousePos
,
getHideSubItemsInterval
,
documentMouseMove
,
getHeaderHeight
,
setSidebar
,
}
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>'
;
el
.
innerHTML
=
'<div class="sidebar-sub-level-items"></div>'
;
});
it
(
'returns 0 if currentOpenMenu is nil'
,
()
=>
{
expect
(
getHideSubItemsInterval
(),
).
toBe
(
0
);
});
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
);
el
.
querySelector
(
'.sidebar-sub-level-items'
).
style
.
display
,
).
toBe
(
''
);
});
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
,
});
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
,
).
to
Be
(
'block
'
);
subItems
.
classList
.
remove
,
).
to
HaveBeenCalledWith
(
'is-above
'
);
});
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'
));
it
(
'does nothing if el has no sub-items'
,
()
=>
{
el
.
innerHTML
=
''
;
documentMouseMove
({
clientX
:
el
.
getBoundingClientRect
().
left
+
20
,
clientY
:
el
.
getBoundingClientRect
().
top
+
10
,
});
mouseEnterTopItems
(
el
);
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
(
...
...
@@ -283,7 +183,7 @@ describe('Fly out sidebar navigation', () => {
describe
(
'canShowActiveSubItems'
,
()
=>
{
afterEach
(()
=>
{
Cookies
.
remove
(
'sidebar_collapsed'
);
setSidebar
(
null
);
});
it
(
'returns true by default'
,
()
=>
{
...
...
@@ -292,36 +192,23 @@ describe('Fly out sidebar navigation', () => {
).
toBeTruthy
();
});
it
(
'returns false when
cookie is false & element is active
'
,
()
=>
{
Cookies
.
set
(
'sidebar_collapsed'
,
'false
'
);
it
(
'returns false when
active & expanded sidebar
'
,
()
=>
{
const
sidebar
=
document
.
createElement
(
'div
'
);
el
.
classList
.
add
(
'active'
);
expect
(
canShowActiveSubItems
(
el
),
).
toBeFalsy
();
});
it
(
'returns true when cookie is false & element is active'
,
()
=>
{
Cookies
.
set
(
'sidebar_collapsed'
,
'true'
);
el
.
classList
.
add
(
'active'
);
setSidebar
(
sidebar
);
expect
(
canShowActiveSubItems
(
el
),
).
toBe
Truth
y
();
).
toBe
Fals
y
();
});
it
(
'returns true when element is active & breakpoint is sm'
,
()
=>
{
breakpointSize
=
'sm'
;
it
(
'returns true when active & collapsed sidebar'
,
()
=>
{
const
sidebar
=
document
.
createElement
(
'div'
);
sidebar
.
classList
.
add
(
'sidebar-icons-only'
);
el
.
classList
.
add
(
'active'
);
expect
(
canShowActiveSubItems
(
el
),
).
toBeTruthy
();
});
it
(
'returns true when element is active & breakpoint is md'
,
()
=>
{
breakpointSize
=
'md'
;
el
.
classList
.
add
(
'active'
);
setSidebar
(
sidebar
);
expect
(
canShowActiveSubItems
(
el
),
...
...
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