diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js index 2f2df7c12b..07e45ff0f7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbstickybar.directive.js @@ -4,7 +4,7 @@ @restrict A @description -Use this directive make an element sticky and follow the page when scrolling. +Use this directive make an element sticky and follow the page when scrolling. `umb-sticky-bar--active` class is applied when the element is stuck

Markup example

@@ -12,140 +12,102 @@ Use this directive make an element sticky and follow the page when scrolling.
 
         
+ umb-sticky-bar>
-

CSS example

-
-    .my-sticky-bar {
-        padding: 15px 0;
-        background: #000000;
-        position: relative;
-        top: 0;
-    }
-
-    .my-sticky-bar.-umb-sticky-bar {
-        top: 100px;
-    }
-
- -@param {string} scrollableContainer Set the class (".element") or the id ("#element") of the scrollable container element. **/ (function () { 'use strict'; - function StickyBarDirective($rootScope) { + function StickyBarDirective() { + + /** + On initial load, the intersector fires if the grid editor is in the viewport + This flag is used to suppress the setClass behaviour on the initial load + **/ + var initial = true; + + /** + Toggle `umb-sticky-bar--active` class on the sticky-bar element + **/ + function setClass(addClass, current) { + if (!initial) { + current.classList.toggle('umb-sticky-bar--active', addClass); + } else { + initial = false; + } + } + + /** + Inserts two elements in the umbStickyBar parent element + These are used by the IntersectionObserve to calculate scroll position + **/ + function addSentinels(current) { + ['-top', '-bottom'].forEach(s => { + const sentinel = document.createElement('div'); + sentinel.classList.add('umb-sticky-sentinel', s); + current.parentElement.appendChild(sentinel); + }); + } + + /** + Calls into setClass when the footer sentinel enters/exits the bottom of the container + Container is the parent element of the umbStickyBar element + **/ + function observeFooter(current, container) { + const observer = new IntersectionObserver((records, observer) => { + let [target, rootBounds, intersected] = [records[0].boundingClientRect, records[0].rootBounds, records[0].intersectionRatio === 1]; + + if (target.bottom > rootBounds.top && intersected) { + setClass(true, current); + } + if (target.top < rootBounds.top && target.bottom < rootBounds.bottom) { + setClass(false, current); + } + }, { + threshold: [1], + root: container + }); + + observer.observe(current.parentElement.querySelector('.umb-sticky-sentinel.-bottom')); + } + + /** + Calls into setClass when the header sentinel enters/exits the top of the container + Container is the parent element of the umbStickyBar element + **/ + function observeHeader(current, container) { + const observer = new IntersectionObserver((records, observer) => { + let [target, rootBounds] = [records[0].boundingClientRect, records[0].rootBounds]; + + if (target.bottom < rootBounds.top) { + setClass(true, current); + } + + if (target.bottom >= rootBounds.top && target.bottom < rootBounds.bottom) { + setClass(false, current); + } + }, { + threshold: [0], + root: container + }); + + observer.observe(current.parentElement.querySelector('.umb-sticky-sentinel.-top')); + } function link(scope, el, attr, ctrl) { - var bar = $(el); - var scrollableContainer = null; - var clonedBar = null; - var cloneIsMade = false; + let current = el[0]; + let container = current.closest('[data-element="editor-container"]'); - function activate() { - - if (bar.parents(".umb-property").length > 1) { - bar.addClass("nested"); - return; - } - - if (attr.scrollableContainer) { - scrollableContainer = bar.closest(attr.scrollableContainer); - } else { - scrollableContainer = $(window); - } - - scrollableContainer.on('scroll.umbStickyBar', determineVisibility).trigger("scroll"); - $(window).on('resize.umbStickyBar', determineVisibility); - - scope.$on('$destroy', function () { - scrollableContainer.off('.umbStickyBar'); - $(window).off('.umbStickyBar'); - }); - - } - - function determineVisibility() { - - var barTop = bar[0].offsetTop; - var scrollTop = scrollableContainer.scrollTop(); - - if (scrollTop > barTop) { - - if (!cloneIsMade) { - - createClone(); - - clonedBar.css({ - 'visibility': 'visible' - }); - - } else { - - calculateSize(); - - } - - } else { - - if (cloneIsMade) { - - //remove cloned element (switched places with original on creation) - bar.remove(); - bar = clonedBar; - clonedBar = null; - - bar.removeClass('-umb-sticky-bar'); - bar.css({ - position: 'relative', - 'width': 'auto', - 'height': 'auto', - 'z-index': 'auto', - 'visibility': 'visible' - }); - - cloneIsMade = false; - - } - - } - - } - - function calculateSize() { - var width = bar.innerWidth(); - clonedBar.css({ - width: width + 10 // + 10 (5*2) because we need to add border to avoid seeing the shadow beneath. Look at the CSS. - }); - } - - function createClone() { - //switch place with cloned element, to keep binding intact - clonedBar = bar; - bar = clonedBar.clone(); - clonedBar.after(bar); - clonedBar.addClass('-umb-sticky-bar'); - clonedBar.css({ - 'position': 'fixed', - // if you change this z-index value, make sure the sticky editor sub headers do not - // clash with umb-dropdown (e.g. the content actions dropdown in content list view) - 'z-index': 99, - 'visibility': 'hidden' - }); - - cloneIsMade = true; - calculateSize(); - - } - - activate(); + addSentinels(current); + observeHeader(current, container); + observeFooter(current, container); } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 78cccac57a..6cf3598638 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -32,15 +32,32 @@ border-radius: 3px; } -.umb-editor-sub-header.-umb-sticky-bar { - box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); +[umb-sticky-bar] { transition: box-shadow 240ms; - margin-top: 0; + margin-top: 0; margin-bottom: 0; - top: calc(@appHeaderHeight + @editorHeaderHeight); + position:sticky; + z-index: 99; - .umb-editor--infinityMode & { - top: calc(@editorHeaderHeight); + &.umb-sticky-bar--active { + box-shadow: 0 6px 3px -3px rgba(0,0,0,.16); + } +} + +.umb-sticky-sentinel { + position: absolute; + left: 0; + width: 100%; + pointer-events: none; + + &.-top { + top:0px; + height:1px; + } + + &.-bottom { + bottom:50px; + height:10px; } } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 9e8dd37ab9..92351d09ca 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -5,6 +5,7 @@ // -------------------------------------------------- .umb-property-editor { width: 100%; + position:relative; } .umb-property-editor-tiny { @@ -165,8 +166,6 @@ .sp-replacer { display: inline-flex; margin-right: 18px; - border: solid 1px @gray-8; - border-radius: 3px; } label { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html index c385223baf..140931ec4b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/subheader/umb-editor-sub-header.html @@ -1,6 +1,5 @@