Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #279 from ckeditor/t/277
Browse files Browse the repository at this point in the history
Feature: `StickyToolbarView` now supports a configurable vertical offset from the top of the page. Closes #277.

Also implemented the `normalizeToolbarConfig()` utility.

BREAKING CHANGE: `StickyToolbarView#limiterOffset` has been renamed to `StickyToolbarView#limiterBottomOffset`.
  • Loading branch information
Reinmar committed Jul 24, 2017
2 parents d83d07d + b3e6088 commit 245f0fa
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 37 deletions.
35 changes: 35 additions & 0 deletions src/toolbar/normalizetoolbarconfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

/**
* @module ui/toolbar/normalizetoolbarconfig
*/

/**
* Normalizes the toolbar configuration (`config.toolbar`), which may be defined as an `Array`
*
* toolbar: [ 'headings', 'bold', 'italic', 'link', 'unlink', ... ]
*
* or an `Object`
*
* toolbar: {
* items: [ 'headings', 'bold', 'italic', 'link', 'unlink', ... ],
* ...
* }
*
* and returns it in the object form.
*
* @param {Array|Object} config The value of `config.toolbar`.
* @returns {Object} A normalized toolbar config object.
*/
export default function normalizeToolbarConfig( config ) {
if ( Array.isArray( config ) ) {
config = {
items: config
};
}

return config;
}
56 changes: 47 additions & 9 deletions src/toolbar/sticky/stickytoolbarview.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,25 @@ export default class StickyToolbarView extends ToolbarView {
* @readonly
* @observable
* @default 50
* @member {Number} #limiterOffset
* @member {Number} #limiterBottomOffset
*/
this.set( 'limiterOffset', 50 );
this.set( 'limiterBottomOffset', 50 );

/**
* The offset from the top edge of the web browser's viewport which makes the
* toolbar become sticky. The default value is `0`, which means the toolbar becomes
* sticky when it's upper edge touches the top of the page viewport.
*
* This attribute is useful when the web page has UI elements positioned to the top
* either using `position: fixed` or `position: sticky`, which would cover the
* sticky toolbar or vice–versa (depending on the `z-index` hierarchy).
*
* @readonly
* @observable
* @default 0
* @member {Number} #viewportTopOffset
*/
this.set( 'viewportTopOffset', 0 );

/**
* Controls the `margin-left` CSS style of the toolbar.
Expand All @@ -93,6 +109,18 @@ export default class StickyToolbarView extends ToolbarView {
*/
this.set( '_isStickyToTheLimiter', false );

/**
* Set `true` if the sticky toolbar uses the {@link #viewportTopOffset},
* i.e. not {@link #_isStickyToTheLimiter} and the {@link #viewportTopOffset}
* is not `0`.
*
* @protected
* @readonly
* @observable
* @member {Boolean} #_hasViewportTopOffset
*/
this.set( '_hasViewportTopOffset', false );

/**
* The DOM bounding client rect of the {@link module:ui/view~View#element} of the toolbar.
*
Expand Down Expand Up @@ -120,8 +148,12 @@ export default class StickyToolbarView extends ToolbarView {
return isSticky ? toPx( this._elementPlaceholder.getBoundingClientRect().width ) : null;
} ),

top: bind.to( '_hasViewportTopOffset', _hasViewportTopOffset => {
return _hasViewportTopOffset ? toPx( this.viewportTopOffset ) : null;
} ),

bottom: bind.to( '_isStickyToTheLimiter', _isStickyToTheLimiter => {
return _isStickyToTheLimiter ? toPx( this.limiterOffset ) : null;
return _isStickyToTheLimiter ? toPx( this.limiterBottomOffset ) : null;
} ),

marginLeft: bind.to( '_marginLeft' )
Expand Down Expand Up @@ -160,6 +192,9 @@ export default class StickyToolbarView extends ToolbarView {

this.element.parentNode.insertBefore( this._elementPlaceholder, this.element );

// Check if the toolbar should go into the sticky state immediately.
this._checkIfShouldBeSticky();

// Update sticky state of the toolbar as the window is being scrolled.
this.listenTo( global.window, 'scroll', () => {
this._checkIfShouldBeSticky();
Expand Down Expand Up @@ -191,23 +226,26 @@ export default class StickyToolbarView extends ToolbarView {

// The toolbar must be active to become sticky.
this.isSticky = this.isActive &&
// The limiter's top edge must be beyond the upper edge of the visible viewport.
limiterRect.top < 0 &&
// The model#limiterElement's height mustn't be smaller than the toolbar's height and model#limiterOffset.
// The limiter's top edge must be beyond the upper edge of the visible viewport (+the viewportTopOffset).
limiterRect.top < this.viewportTopOffset &&
// The model#limiterElement's height mustn't be smaller than the toolbar's height and model#limiterBottomOffset.
// There's no point in entering the sticky mode if the model#limiterElement is very, very small, because
// it would immediately set model#_isStickyToTheLimiter true and, given model#limiterOffset, the toolbar
// it would immediately set model#_isStickyToTheLimiter true and, given model#limiterBottomOffset, the toolbar
// would be positioned before the model#limiterElement.
this._toolbarRect.height + this.limiterOffset < limiterRect.height;
this._toolbarRect.height + this.limiterBottomOffset < limiterRect.height;

// Stick the toolbar to the top edge of the viewport simulating CSS position:sticky.
// TODO: Possibly replaced by CSS in the future http://caniuse.com/#feat=css-sticky
if ( this.isSticky ) {
this._isStickyToTheLimiter = limiterRect.bottom < toolbarRect.height + this.limiterOffset;
this._isStickyToTheLimiter =
limiterRect.bottom < toolbarRect.height + this.limiterBottomOffset + this.viewportTopOffset;
this._hasViewportTopOffset = !this._isStickyToTheLimiter && !!this.viewportTopOffset;
this._marginLeft = this._isStickyToTheLimiter ? null : toPx( -global.window.scrollX );
}
// Detach the toolbar from the top edge of the viewport.
else {
this._isStickyToTheLimiter = false;
this._hasViewportTopOffset = false;
this._marginLeft = null;
}
}
Expand Down
85 changes: 66 additions & 19 deletions tests/manual/stickytoolbarview/stickytoolbarview.html
Original file line number Diff line number Diff line change
@@ -1,31 +1,70 @@
<div class="ck-reset ck-editor">
<div class="ck-reset_all ck-editor__top">
<div id="columns">
<div class="column">
<h2>Sticky to the top of the viewport</h2>

<div class="ck-reset ck-editor ck-sticky_to-the-top">
<div class="ck-reset_all ck-editor__top"></div>
<p class="ck-reset">
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
</p>
<div class="ck-reset_all offset-visualizer"></div>
</div>
</div>
<p class="ck-reset">
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
</p>
<div class="ck-reset_all offset-visualizer">
<div class="column">
<h2>Sticky to the green box</h2>

<div class="ck-reset ck-editor ck-sticky_to-the-box">
<div class="ck-reset_all ck-editor__top "></div>
<p class="ck-reset">
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
An editable content mock–up.
</p>
<div class="ck-reset_all offset-visualizer"></div>
</div>

<div id="fixed">The toolbar should stick to me instead of the viewport.</div>
</div>
</div>

<style>
body {
width: 3000px;
height: 3000px;
padding-top: 150px;
}

#columns {
overflow: hidden;
}

.column {
float: left;
width: 350px;
padding: 1em;
}

.ck-editor {
height: 200px;
max-width: 400px;
margin: 5em;
background: #ccc;
position: relative;
}

.ck-toolbar {
background: #fff !important;
background: yellow !important;
padding: 1em;
}

.ck-toolbar:after {
Expand All @@ -45,8 +84,16 @@
content: "An offset mock–up. Toolbar should *never* cover the red area.";
}

body {
width: 3000px;
height: 3000px;
#fixed {
height: 100px;
width: 350px;
position: fixed;
top: 0;
z-index: 9999;
background: green;
opacity: .8;
color: #fff;
box-sizing: border-box;
padding: 30px;
}
</style>
10 changes: 8 additions & 2 deletions tests/manual/stickytoolbarview/stickytoolbarview.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ import StickyToolbarView from '../../../src/toolbar/sticky/stickytoolbarview';
import '@ckeditor/ckeditor5-theme-lark/theme/theme.scss';

const ui = testUtils.createTestUIView( {
top: '.ck-editor__top'
stickyToTheTop: '.ck-sticky_to-the-top .ck-editor__top',
stickyToTheBox: '.ck-sticky_to-the-box .ck-editor__top'
} );

createToolbar( ui.top );
createToolbar( ui.stickyToTheTop );
const stickyToTheBoxToolbar = createToolbar( ui.stickyToTheBox );

stickyToTheBoxToolbar.viewportTopOffset = 100;

function createToolbar( collection ) {
const toolbar = new StickyToolbarView();
Expand All @@ -21,4 +25,6 @@ function createToolbar( collection ) {

collection.add( toolbar );
toolbar.isActive = true;

return toolbar;
}
9 changes: 9 additions & 0 deletions tests/manual/stickytoolbarview/stickytoolbarview.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
## Vertical scrolling

### Sticky to the top of the viewport

1. When the page is scrolled vertically, the toolbar should
1. stick to the top of the viewport first,
1. then disappear beyond the upper edge of the viewport as it touches the red area
1. but never cover the red area or go beyond the upper edge of editor mock–up.

### Sticky to the green box

1. When the page is scrolled vertically, the toolbar should
1. stick to the bottom of the green box first,
1. then disappear beyond the bottom edge of the green box as it touches the red area
1. but never cover the red area or go beyond the upper edge of editor mock–up.

## Horizontal scrolling

1. The toolbar should always fit horizontally within the editor mock–up, regardless of the position of the h– and v–scrolls of the web page.
Expand Down
28 changes: 28 additions & 0 deletions tests/toolbar/normalizetoolbarconfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

import normalizeToolbarConfig from '../../src/toolbar/normalizetoolbarconfig';

describe( 'normalizeToolbarConfig()', () => {
it( 'normalizes the config specified as an Array', () => {
const cfg = [ 'foo', 'bar' ];
const normalized = normalizeToolbarConfig( cfg );

expect( normalized ).to.be.an( 'object' );
expect( normalized.items ).to.equal( cfg );
} );

it( 'passes through an already normalized config', () => {
const cfg = {
items: [ 'foo', 'bar' ],
foo: 'bar'
};
const normalized = normalizeToolbarConfig( cfg );

expect( normalized ).to.equal( cfg );
expect( normalized.items ).to.equal( cfg.items );
expect( normalized.foo ).to.equal( cfg.foo );
} );
} );
Loading

0 comments on commit 245f0fa

Please sign in to comment.