Feature: mobile ux improvements (#802)

* Inline Toolbar now works on mobile devices

* Some styles updates

* Inline Toolbar now detects left and rigth sides of content

* listen selection change only on touch devices

* improve toolbar paddings

* invalidate content rectangle cache on resize

* Toolbar now works on mobile devices

* prod bundle

* add changelog

* upd texts

* do not hide toolbar on window resize

* rm sever
This commit is contained in:
Peter Savchenko 2019-06-20 21:30:48 +03:00 committed by GitHub
commit 5aeb65b06f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 354 additions and 1040 deletions

8
dist/editor.js vendored

File diff suppressed because one or more lines are too long

12
dist/sprite.svg vendored
View file

@ -8,9 +8,7 @@
<path d="M8.024 4.1v8.6a1.125 1.125 0 0 1-2.25 0V4.1L2.18 7.695A1.125 1.125 0 1 1 .59 6.104L6.103.588c.44-.439 1.151-.439 1.59 0l5.516 5.516a1.125 1.125 0 0 1-1.59 1.59L8.023 4.1z"/>
</symbol>
<symbol id="bold" viewBox="0 0 13 15">
<path d="M5.996 13.9H1.752c-.613 0-1.05-.137-1.312-.412-.262-.275-.393-.712-.393-1.312V1.737C.047 1.125.18.684.449.416.718.147 1.152.013 1.752.013h4.5a10.5 10.5 0 0 1 1.723.123c.487.082.922.24 1.308.474a3.43 3.43 0 0 1 1.449 1.738c.132.363.199.747.199 1.151 0 1.39-.695 2.406-2.084 3.05 1.825.581 2.737 1.712 2.737 3.391 0 .777-.199 1.477-.596 2.099a3.581 3.581 0 0 1-1.61 1.378c-.424.177-.91.301-1.46.374-.549.073-1.19.109-1.922.109zm-.209-6.167H2.86v4.055h3.022c1.9 0 2.851-.686 2.851-2.056 0-.7-.246-1.21-.739-1.525-.492-.316-1.228-.474-2.207-.474zM2.86 2.125v3.59h2.577c.7 0 1.242-.066 1.624-.198a1.55 1.55 0 0 0 .876-.758c.158-.265.237-.562.237-.89 0-.702-.25-1.167-.748-1.398-.499-.23-1.26-.346-2.283-.346H2.86z"/>
<symbol id="bold"><path d="M5.997 14H1.72c-.618 0-1.058-.138-1.323-.415C.132 13.308 0 12.867 0 12.262V1.738C0 1.121.135.676.406.406.676.136 1.114 0 1.719 0h4.536c.669 0 1.248.041 1.738.124.49.083.93.242 1.318.478a3.458 3.458 0 0 1 1.461 1.752c.134.366.2.753.2 1.16 0 1.401-.7 2.426-2.1 3.075 1.84.586 2.76 1.726 2.76 3.42 0 .782-.2 1.487-.602 2.114a3.61 3.61 0 0 1-1.623 1.39 5.772 5.772 0 0 1-1.471.377c-.554.073-1.2.11-1.939.11zm-.21-6.217h-2.95v4.087h3.046c1.916 0 2.874-.69 2.874-2.072 0-.707-.248-1.22-.745-1.537-.496-.319-1.238-.478-2.225-.478zM2.837 2.13v3.619h2.597c.707 0 1.252-.067 1.638-.2.385-.134.68-.389.883-.765.16-.267.239-.566.239-.897 0-.707-.252-1.176-.755-1.409-.503-.232-1.27-.348-2.301-.348H2.836z"/>
</symbol>
<symbol id="cross" viewBox="0 0 237 237">
<path transform="rotate(45 280.675 51.325)" d="M191 191V73c0-5.523 4.477-10 10-10h25c5.523 0 10 4.477 10 10v118h118c5.523 0 10 4.477 10 10v25c0 5.523-4.477 10-10 10H236v118c0 5.523-4.477 10-10 10h-25c-5.523 0-10-4.477-10-10V236H73c-5.523 0-10-4.477-10-10v-25c0-5.523 4.477-10 10-10h118z"/>
@ -24,13 +22,11 @@
</g>
</symbol>
<symbol id="italic" viewBox="0 0 6 15">
<path d="M4 5.2l-1.368 7.474c-.095.518-.29.91-.585 1.175a1.468 1.468 0 0 1-1.01.398c-.379 0-.662-.136-.85-.407-.186-.272-.234-.66-.141-1.166L1.4 5.276c.093-.511.282-.896.567-1.155a1.43 1.43 0 0 1 .994-.389c.38 0 .668.13.867.389.199.259.256.618.172 1.08zm-.79-2.67c-.36 0-.648-.111-.863-.332-.215-.221-.286-.534-.212-.938.067-.366.253-.668.559-.905A1.57 1.57 0 0 1 3.673 0c.334 0 .612.107.831.322.22.215.292.527.217.938-.073.398-.256.709-.55.933a1.55 1.55 0 0 1-.961.336z"/>
<symbol id="italic">
<path d="M19.211 15.326l-1.44 7.108c-.1.493-.305.865-.615 1.117a1.64 1.64 0 0 1-1.064.379c-.4 0-.697-.13-.894-.388-.197-.258-.247-.627-.15-1.108l1.426-7.036c.098-.486.297-.853.597-1.1.299-.245.648-.368 1.047-.368.399 0 .703.123.912.369.21.246.27.588.181 1.027zm-.831-2.663c-.38 0-.682-.116-.909-.35-.227-.232-.301-.561-.223-.987.07-.385.266-.703.588-.952.322-.25.665-.374 1.03-.374.353 0 .645.113.876.34.232.225.308.554.229.986-.077.42-.27.747-.58.983-.308.236-.646.354-1.011.354z"/>
</symbol>
<symbol id="link" viewBox="0 0 15 14">
<path transform="rotate(-45 11.83 6.678)" d="M11.332 4.013a51.07 51.07 0 0 1-2.28.001A1.402 1.402 0 0 0 7.7 2.25H3.65a1.4 1.4 0 1 0 0 2.8h.848c.206.86.693 1.61 1.463 2.25H3.65a3.65 3.65 0 1 1 0-7.3H7.7a3.65 3.65 0 0 1 3.632 4.013zM10.9 0h2a3.65 3.65 0 0 1 0 7.3H8.85a3.65 3.65 0 0 1-3.632-4.011A62.68 62.68 0 0 1 7.5 3.273 1.401 1.401 0 0 0 8.85 5.05h4.05a1.4 1.4 0 0 0 0-2.8h-.48C12.274 1.664 11.694.785 10.9 0z"/>
<symbol id="link"><path d="M15.439 21.153a4.202 4.202 0 0 0 2.72.63l-.985.986a4.202 4.202 0 1 1-5.943-5.945l2.093-2.093a4.202 4.202 0 0 1 5.934-.009l-1.655 1.656a5.886 5.886 0 0 1-.02.019 1.835 1.835 0 0 0-2.585.009l-2.093 2.093a1.836 1.836 0 0 0 2.534 2.654zm3.122-8.306a4.202 4.202 0 0 0-2.72-.63l.985-.986a4.202 4.202 0 1 1 5.943 5.945l-2.093 2.093a4.202 4.202 0 0 1-5.934.009l1.655-1.656.02-.019a1.835 1.835 0 0 0 2.585-.009l2.093-2.093a1.836 1.836 0 0 0-2.534-2.654z"/>
</symbol>
<symbol id="plus" viewBox="0 0 14 14">
<path d="M8.05 5.8h4.625a1.125 1.125 0 0 1 0 2.25H8.05v4.625a1.125 1.125 0 0 1-2.25 0V8.05H1.125a1.125 1.125 0 0 1 0-2.25H5.8V1.125a1.125 1.125 0 0 1 2.25 0V5.8z"/>

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Before After
Before After

View file

@ -1,5 +1,9 @@
# Changelog
### 2.15
- `Improvements` — Inline Toolbar now works on mobile devices [#706](https://github.com/codex-team/editor.js/issues/706)
- `Improvements` — Toolbar looks better on mobile devices [#706](https://github.com/codex-team/editor.js/issues/706)
### 2.14

View file

@ -1,3 +1 @@
<svg width="13" height="15" viewBox="0 0 13 15" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M5.996 13.9H1.752c-.613 0-1.05-.137-1.312-.412-.262-.275-.393-.712-.393-1.312V1.737C.047 1.125.18.684.449.416.718.147 1.152.013 1.752.013h4.5a10.5 10.5 0 0 1 1.723.123c.487.082.922.24 1.308.474a3.43 3.43 0 0 1 1.449 1.738c.132.363.199.747.199 1.151 0 1.39-.695 2.406-2.084 3.05 1.825.581 2.737 1.712 2.737 3.391 0 .777-.199 1.477-.596 2.099a3.581 3.581 0 0 1-1.61 1.378c-.424.177-.91.301-1.46.374-.549.073-1.19.109-1.922.109zm-.209-6.167H2.86v4.055h3.022c1.9 0 2.851-.686 2.851-2.056 0-.7-.246-1.21-.739-1.525-.492-.316-1.228-.474-2.207-.474zM2.86 2.125v3.59h2.577c.7 0 1.242-.066 1.624-.198a1.55 1.55 0 0 0 .876-.758c.158-.265.237-.562.237-.89 0-.702-.25-1.167-.748-1.398-.499-.23-1.26-.346-2.283-.346H2.86z"/>
</svg>
<svg width="12" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M5.997 14H1.72c-.618 0-1.058-.138-1.323-.415C.132 13.308 0 12.867 0 12.262V1.738C0 1.121.135.676.406.406.676.136 1.114 0 1.719 0h4.536c.669 0 1.248.041 1.738.124.49.083.93.242 1.318.478a3.458 3.458 0 0 1 1.461 1.752c.134.366.2.753.2 1.16 0 1.401-.7 2.426-2.1 3.075 1.84.586 2.76 1.726 2.76 3.42 0 .782-.2 1.487-.602 2.114a3.61 3.61 0 0 1-1.623 1.39 5.772 5.772 0 0 1-1.471.377c-.554.073-1.2.11-1.939.11zm-.21-6.217h-2.95v4.087h3.046c1.916 0 2.874-.69 2.874-2.072 0-.707-.248-1.22-.745-1.537-.496-.319-1.238-.478-2.225-.478zM2.837 2.13v3.619h2.597c.707 0 1.252-.067 1.638-.2.385-.134.68-.389.883-.765.16-.267.239-.566.239-.897 0-.707-.252-1.176-.755-1.409-.503-.232-1.27-.348-2.301-.348H2.836z"/></svg>

Before

Width:  |  Height:  |  Size: 857 B

After

Width:  |  Height:  |  Size: 774 B

Before After
Before After

View file

@ -1,3 +1,3 @@
<svg width="6" height="15" viewBox="0 0 6 15" xmlns="http://www.w3.org/2000/svg">
<path d="M4 5.2l-1.368 7.474c-.095.518-.29.91-.585 1.175a1.468 1.468 0 0 1-1.01.398c-.379 0-.662-.136-.85-.407-.186-.272-.234-.66-.141-1.166L1.4 5.276c.093-.511.282-.896.567-1.155a1.43 1.43 0 0 1 .994-.389c.38 0 .668.13.867.389.199.259.256.618.172 1.08zm-.79-2.67c-.36 0-.648-.111-.863-.332-.215-.221-.286-.534-.212-.938.067-.366.253-.668.559-.905A1.57 1.57 0 0 1 3.673 0c.334 0 .612.107.831.322.22.215.292.527.217.938-.073.398-.256.709-.55.933a1.55 1.55 0 0 1-.961.336z" />
<svg width="34" height="34" xmlns="http://www.w3.org/2000/svg">
<path d="M19.211 15.326l-1.44 7.108c-.1.493-.305.865-.615 1.117a1.64 1.64 0 0 1-1.064.379c-.4 0-.697-.13-.894-.388-.197-.258-.247-.627-.15-1.108l1.426-7.036c.098-.486.297-.853.597-1.1.299-.245.648-.368 1.047-.368.399 0 .703.123.912.369.21.246.27.588.181 1.027zm-.831-2.663c-.38 0-.682-.116-.909-.35-.227-.232-.301-.561-.223-.987.07-.385.266-.703.588-.952.322-.25.665-.374 1.03-.374.353 0 .645.113.876.34.232.225.308.554.229.986-.077.42-.27.747-.58.983-.308.236-.646.354-1.011.354z"/>
</svg>

Before

Width:  |  Height:  |  Size: 566 B

After

Width:  |  Height:  |  Size: 557 B

Before After
Before After

View file

@ -1,3 +1 @@
<svg width="15" height="14" viewBox="0 0 15 14" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path transform="rotate(-45 11.83 6.678)" d="M11.332 4.013a51.07 51.07 0 0 1-2.28.001A1.402 1.402 0 0 0 7.7 2.25H3.65a1.4 1.4 0 1 0 0 2.8h.848c.206.86.693 1.61 1.463 2.25H3.65a3.65 3.65 0 1 1 0-7.3H7.7a3.65 3.65 0 0 1 3.632 4.013zM10.9 0h2a3.65 3.65 0 0 1 0 7.3H8.85a3.65 3.65 0 0 1-3.632-4.011A62.68 62.68 0 0 1 7.5 3.273 1.401 1.401 0 0 0 8.85 5.05h4.05a1.4 1.4 0 0 0 0-2.8h-.48C12.274 1.664 11.694.785 10.9 0z"/>
</svg>
<svg width="34" height="34" xmlns="http://www.w3.org/2000/svg"><path d="M15.439 21.153a4.202 4.202 0 0 0 2.72.63l-.985.986a4.202 4.202 0 1 1-5.943-5.945l2.093-2.093a4.202 4.202 0 0 1 5.934-.009l-1.655 1.656a5.886 5.886 0 0 1-.02.019 1.835 1.835 0 0 0-2.585.009l-2.093 2.093a1.836 1.836 0 0 0 2.534 2.654zm3.122-8.306a4.202 4.202 0 0 0-2.72-.63l.985-.986a4.202 4.202 0 1 1 5.943 5.945l-2.093 2.093a4.202 4.202 0 0 1-5.934.009l1.655-1.656.02-.019a1.835 1.835 0 0 0 2.585-.009l2.093-2.093a1.836 1.836 0 0 0-2.534-2.654z"/></svg>

Before

Width:  |  Height:  |  Size: 554 B

After

Width:  |  Height:  |  Size: 526 B

Before After
Before After

View file

@ -56,7 +56,7 @@ export default class BoldInlineTool implements InlineTool {
this.nodes.button = document.createElement('button') as HTMLButtonElement;
this.nodes.button.type = 'button';
this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier);
this.nodes.button.appendChild($.svg('bold', 13, 15));
this.nodes.button.appendChild($.svg('bold', 12, 14));
return this.nodes.button;
}

View file

@ -56,7 +56,7 @@ export default class ItalicInlineTool implements InlineTool {
this.nodes.button = document.createElement('button') as HTMLButtonElement;
this.nodes.button.type = 'button';
this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier);
this.nodes.button.appendChild($.svg('italic', 6, 15));
this.nodes.button.appendChild($.svg('italic', 34, 34));
return this.nodes.button;
}

View file

@ -112,7 +112,7 @@ export default class LinkInlineTool implements InlineTool {
this.nodes.button = document.createElement('button') as HTMLButtonElement;
this.nodes.button.type = 'button';
this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier);
this.nodes.button.appendChild($.svg('link', 15, 14));
this.nodes.button.appendChild($.svg('link', 34, 34));
this.nodes.button.appendChild($.svg('unlink', 16, 18));
return this.nodes.button;
}

View file

@ -22,9 +22,9 @@ export interface ListenerData {
handler: (event: Event) => void;
/**
* Should event bubbling be used or not
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
*/
useCapture: boolean;
options: boolean | AddEventListenerOptions;
}
/**
@ -56,19 +56,19 @@ export default class Listeners extends Module {
* @param {EventTarget} element - DOM element that needs to be listened
* @param {String} eventType - event type
* @param {Function} handler - method that will be fired on event
* @param {Boolean} useCapture - use event bubbling
* @param {Boolean|AddEventListenerOptions} options - useCapture or {capture, passive, once}
*/
public on(
element: EventTarget,
eventType: string,
handler: (event: Event) => void,
useCapture: boolean = false,
options: boolean | AddEventListenerOptions = false,
): void {
const assignedEventData = {
element,
eventType,
handler,
useCapture,
options,
};
const alreadyExist = this.findOne(element, eventType, handler);
@ -76,7 +76,7 @@ export default class Listeners extends Module {
if (alreadyExist) { return; }
this.allListeners.push(assignedEventData);
element.addEventListener(eventType, handler, useCapture);
element.addEventListener(eventType, handler, options);
}
/**
@ -85,13 +85,13 @@ export default class Listeners extends Module {
* @param {EventTarget} element - DOM element that we removing listener
* @param {String} eventType - event type
* @param {Function} handler - remove handler, if element listens several handlers on the same event type
* @param {Boolean} useCapture - use event bubbling
* @param {Boolean|AddEventListenerOptions} options - useCapture or {capture, passive, once}
*/
public off(
element: EventTarget,
eventType: string,
handler: (event: Event) => void,
useCapture: boolean = false,
options: boolean | AddEventListenerOptions = false,
): void {
const existingListeners = this.findAll(element, eventType, handler);
@ -103,7 +103,7 @@ export default class Listeners extends Module {
}
});
element.removeEventListener(eventType, handler, useCapture);
element.removeEventListener(eventType, handler, options);
}
/**

View file

@ -196,18 +196,27 @@ export default class Toolbar extends Module {
return;
}
/**
* Set Toolbar Min Height as Block's height
* Plus Button and Toolbox positioned at the center of the Toolbar
*/
const contentOffset = Math.floor(currentBlock.offsetHeight / 2);
const { isMobile } = this.Editor.UI;
const blockHeight = currentBlock.offsetHeight;
let toolbarY = currentBlock.offsetTop;
/**
* 1) On desktop Toolbar at the top of Block, Plus/Toolbox moved the center of Block
* 2) On mobile Toolbar at the bottom of Block
*/
if (!isMobile) {
const contentOffset = Math.floor(blockHeight / 2);
this.nodes.plusButton.style.transform = `translate3d(0, calc(${contentOffset}px - 50%), 0)`;
this.Editor.Toolbox.nodes.toolbox.style.transform = `translate3d(0, calc(${contentOffset}px - 50%), 0)`;
} else {
toolbarY += blockHeight;
}
this.nodes.plusButton.style.transform = `translate3d(0, calc(${contentOffset}px - 50%), 0)`;
this.Editor.Toolbox.nodes.toolbox.style.transform = `translate3d(0, calc(${contentOffset}px - 50%), 0)`;
/**
* Move Toolbar to the Top coordinate of Block
*/
this.nodes.wrapper.style.transform = `translate3D(0, ${Math.floor(currentBlock.offsetTop)}px, 0)`;
this.nodes.wrapper.style.transform = `translate3D(0, ${Math.floor(toolbarY)}px, 0)`;
}
/**

View file

@ -23,6 +23,8 @@ export default class InlineToolbar extends Module {
public CSS = {
inlineToolbar: 'ce-inline-toolbar',
inlineToolbarShowed: 'ce-inline-toolbar--showed',
inlineToolbarLeftOriented: 'ce-inline-toolbar--left-oriented',
inlineToolbarRightOriented: 'ce-inline-toolbar--right-oriented',
buttonsWrapper: 'ce-inline-toolbar__buttons',
actionsWrapper: 'ce-inline-toolbar__actions',
inlineToolButton: 'ce-inline-tool',
@ -79,6 +81,12 @@ export default class InlineToolbar extends Module {
*/
private focusedButtonIndex: number = -1;
/**
* Cache for Inline Toolbar width
* @type {number}
*/
private width: number = 0;
/**
* Inline Toolbar Tools
*
@ -129,6 +137,10 @@ export default class InlineToolbar extends Module {
*/
this.addTools();
/**
* Recalculate initial width with all buttons
*/
this.recalculateWidth();
}
/**
@ -148,6 +160,7 @@ export default class InlineToolbar extends Module {
this.move();
this.open();
this.Editor.Toolbar.close();
/** Check Tools state for selected fragment */
this.checkToolsState();
@ -178,6 +191,19 @@ export default class InlineToolbar extends Module {
newCoords.x += Math.floor(selectionRect.width / 2);
}
/**
* Inline Toolbar has -50% translateX, so we need to check real coords to prevent overflowing
*/
const realLeftCoord = newCoords.x - this.width / 2;
const realRightCoord = newCoords.x + this.width / 2;
/**
* By default, Inline Toolbar has top-corner at the center
* We are adding a modifiers for to move corner to the left or right
*/
this.nodes.wrapper.classList.toggle(this.CSS.inlineToolbarLeftOriented, realLeftCoord < this.Editor.UI.contentRect.left);
this.nodes.wrapper.classList.toggle(this.CSS.inlineToolbarRightOriented, realRightCoord > this.Editor.UI.contentRect.right);
this.nodes.wrapper.style.left = Math.floor(newCoords.x) + 'px';
this.nodes.wrapper.style.top = Math.floor(newCoords.y) + 'px';
}
@ -366,6 +392,18 @@ export default class InlineToolbar extends Module {
if (lastVisibleButton) {
lastVisibleButton.classList.add(this.CSS.inlineToolButtonLast);
}
/**
* Recalculate width because some buttons can be hidden
*/
this.recalculateWidth();
}
/**
* Recalculate inline toolbar width
*/
private recalculateWidth(): void {
this.width = this.nodes.wrapper.offsetWidth;
}
/**

View file

@ -13,6 +13,7 @@ import $ from '../dom';
import _ from '../utils';
import Selection from '../selection';
import Block from '../block';
/**
* @class
@ -53,10 +54,37 @@ export default class UI extends Module {
}
/**
* Width of center column of Editor
* @type {number}
* Return Width of center column of Editor
* @return {DOMRect}
*/
public contentWidth: number = 650;
public get contentRect(): DOMRect {
if (this.contentRectCache) {
return this.contentRectCache;
}
const someBlock = this.nodes.wrapper.querySelector(`.${Block.CSS.content}`);
/**
* When Editor is not ready, there is no Blocks, so return the default value
*/
if (!someBlock) {
return {
width: 650,
left: 0,
right: 0,
} as DOMRect;
}
this.contentRectCache = someBlock.getBoundingClientRect() as DOMRect;
return this.contentRectCache;
}
/**
* Flag that became true on mobile viewport
* @type {boolean}
*/
public isMobile: boolean = false;
/**
* HTML Elements used for UI
@ -67,6 +95,21 @@ export default class UI extends Module {
redactor: null,
};
/**
* Cache for center column rectangle info
* Invalidates on window resize
* @type {DOMRect}
*/
private contentRectCache: DOMRect = undefined;
/**
* Handle window resize only when it finished
* @type {() => void}
*/
private resizeDebouncer: () => void = _.debounce(() => {
this.windowResize();
}, 200);
/**
* Adds loader to editor while content is not ready
*/
@ -88,8 +131,19 @@ export default class UI extends Module {
* Making main interface
*/
public async prepare(): Promise<void> {
/**
* Detect mobile version
*/
this.checkIsMobile();
/**
* Make main UI elements
*/
await this.make();
/**
* Loader for rendering process
*/
this.addLoader();
/**
@ -134,6 +188,13 @@ export default class UI extends Module {
this.nodes.holder.innerHTML = '';
}
/**
* Check for mobile mode and cache a result
*/
private checkIsMobile() {
this.isMobile = window.innerWidth < 650;
}
/**
* Makes Editor.js interface
* @return {Promise<void>}
@ -154,7 +215,7 @@ export default class UI extends Module {
/**
* If Editor has injected into the narrow container, enable Narrow Mode
*/
if (this.nodes.holder.offsetWidth < this.contentWidth) {
if (this.nodes.holder.offsetWidth < this.contentRect.width) {
this.nodes.wrapper.classList.add(this.CSS.editorWrapperNarrow);
}
@ -202,6 +263,36 @@ export default class UI extends Module {
);
this.Editor.Listeners.on(document, 'keydown', (event) => this.documentKeydown(event as KeyboardEvent), true);
this.Editor.Listeners.on(document, 'click', (event) => this.documentClicked(event as MouseEvent), true);
/**
* Handle selection change on mobile devices for the Inline Toolbar support
*/
if (_.isTouchSupported()) {
this.Editor.Listeners.on(document, 'selectionchange', (event) => {
this.selectionChanged(event as Event);
}, true);
}
this.Editor.Listeners.on(window, 'resize', () => {
this.resizeDebouncer();
}, {
passive: true,
});
}
/**
* Resize window handler
*/
private windowResize(): void {
/**
* Invalidate content zone size cached, because it may be changed
*/
this.contentRectCache = null;
/**
* Detect mobile version
*/
this.checkIsMobile();
}
/**
@ -380,8 +471,6 @@ export default class UI extends Module {
* Do not fire check on clicks at the Inline Toolbar buttons
*/
const target = event.target as HTMLElement;
const clickedOnInlineToolbarButton = target.closest(`.${this.Editor.InlineToolbar.CSS.inlineToolbar}`);
const clickedInsideOfEditor = this.nodes.holder.contains(target) || Selection.isAtEditor;
if (!clickedInsideOfEditor) {
@ -396,11 +485,6 @@ export default class UI extends Module {
this.Editor.Toolbar.close();
this.Editor.BlockSelection.clearSelection();
} else if (!clickedOnInlineToolbarButton) {
/**
* Move inline toolbar to the focused Block
*/
this.Editor.InlineToolbar.handleShowingEvent(event);
}
if (Selection.isAtEditor) {
@ -517,7 +601,26 @@ export default class UI extends Module {
}
/**
* Append prebuilded sprite with SVG icons
* Handle selection changes on mobile devices
* Uses for showing the Inline Toolbar
* @param {Event} event
*/
private selectionChanged(event: Event): void {
const focusedElement = Selection.anchorElement as Element;
/**
* Event can be fired on clicks at the Editor elements, for example, at the Inline Toolbar
* We need to skip such firings
*/
if (!focusedElement || !focusedElement.closest(`.${Block.CSS.content}`)) {
return;
}
this.Editor.InlineToolbar.handleShowingEvent(event);
}
/**
* Append prebuilt sprite with SVG icons
*/
private appendSVGSprite(): void {
const spriteHolder = $.make('div');

View file

@ -2,6 +2,7 @@
* TextRange interface fot IE9-
*/
import _ from './utils';
import $ from './dom';
interface TextRange {
boundingTop: number;
@ -55,6 +56,30 @@ export default class SelectionUtils {
return selection ? selection.anchorNode : null;
}
/**
* Returns selected anchor element
* @return {Element|null}
*/
static get anchorElement(): Element | null {
const selection = window.getSelection();
if (!selection) {
return null;
}
const anchorNode = selection.anchorNode;
if (!anchorNode) {
return null;
}
if (!$.isElement(anchorNode)) {
return anchorNode.parentElement;
} else {
return anchorNode;
}
}
/**
* Returns selection offset according to the anchor node
* {@link https://developer.mozilla.org/ru/docs/Web/API/Selection/anchorOffset}

View file

@ -338,4 +338,17 @@ export default class Util {
return Util.deepMerge(target, ...sources);
}
/**
* Return true if current device supports touch events
*
* Note! This is a simple solution, it can give false-positive results.
* To detect touch devices more carefully, use 'touchstart' event listener
* @see http://www.stucox.com/blog/you-cant-detect-a-touchscreen/
*
* @return {boolean}
*/
public static isTouchSupported(): boolean {
return 'ontouchstart' in document.documentElement;
}
}

View file

@ -100,3 +100,20 @@
transform: scale3d(1, 1, 1);
}
}
@keyframes panelShowing {
from {
opacity: 0;
transform: translateY(-8px) scale(0.9);
}
70% {
opacity: 1;
transform: translateY(2px);
}
to {
transform: translateY(0);
}
}

View file

@ -25,11 +25,11 @@
@apply --toolbar-button;
&:not(:nth-child(3n+3)) {
margin-right: 5px;
margin-right: 3px;
}
&:nth-child(n+4) {
margin-top: 5px;
margin-top: 3px;
}
&--active {

View file

@ -1,14 +1,36 @@
.ce-inline-toolbar {
@apply --overlay-pane;
padding: 6px;
transform: translateX(-50%);
display: none;
padding: 3px;
transform: translateX(-50%) translateY(8px) scale(0.9);
box-shadow: 0 6px 12px -6px rgba(131, 147, 173, 0.46),
5px -12px 34px -13px rgba(97, 105, 134, 0.6),
0 26px 52px 3px rgba(147, 165, 186, 0.24);
opacity: 0;
visibility: hidden;
transition: transform 150ms ease, opacity 250ms ease;
will-change: transform, opacity;
&--showed {
display: block;
opacity: 1;
visibility: visible;
transform: translateX(-50%)
}
&--left-oriented {
transform: translateX(-23px) translateY(8px) scale(0.9);
}
&--left-oriented&--showed {
transform: translateX(-23px);
}
&--right-oriented {
transform: translateX(-100%) translateY(8px) scale(0.9);
margin-left: 23px;
}
&--right-oriented&--showed {
transform: translateX(-100%);
}
[hidden] {
@ -25,7 +47,7 @@
line-height: normal;
&:not(:last-of-type) {
margin-right: 5px;
margin-right: 2px;
}
&--last {
@ -52,7 +74,7 @@
outline: none;
border: 0;
border-radius: 3px;
margin: 6px 0 0;
margin: 3px 0 0;
font-size: 13px;
padding: 8px;
width: 100%;

View file

@ -2,7 +2,7 @@
@apply --overlay-pane;
right: 5px;
top: 35px;
min-width: 124px;
min-width: 114px;
@media (--mobile){
bottom: 50px;
@ -23,19 +23,19 @@
&--opened {
display: block;
animation-duration: 0.5s;
animation-name: bounceIn;
animation-duration: 0.1s;
animation-name: panelShowing;
}
&__plugin-zone {
&:not(:empty){
padding: 6px 6px 0;
padding: 3px 3px 0;
}
}
&__default-zone {
&:not(:empty){
padding: 6px;
padding: 3px;
}
}
@ -43,11 +43,11 @@
@apply --toolbar-button;
&:not(:nth-child(3n+3)) {
margin-right: 5px;
margin-right: 3px;
}
&:nth-child(n+4) {
margin-top: 5px;
margin-top: 3px;
}
line-height: 32px;

View file

@ -10,16 +10,9 @@
display: none;
@media (--mobile) {
position: fixed;
bottom: 0;
top: auto;
left: 0;
right: 0;
z-index: 9;
height: 50px;
background: #fff;
box-shadow: 0 -2px 12px rgba(60, 67, 81, 0.18);
transform: none !important;
@apply --overlay-pane;
padding: 3px;
margin-top: 5px;
}
&--opened {
@ -39,9 +32,7 @@
display: flex;
align-content: center;
margin: 0;
padding: 0 10px;
max-width: calc(100% - 70px);
overflow-x: auto;
max-width: calc(100% - 40px);
}
}
@ -50,13 +41,16 @@
position: absolute;
left: calc(var(--toolbox-buttons-size) * -1);
flex-shrink: 0;
&--hidden {
display: none;
}
@media (--mobile){
display: none !important;
display: inline-flex !important;
position: static;
transform: none !important;
}
}
@ -80,6 +74,7 @@
@media (--mobile){
position: static;
margin-left: auto;
padding-right: 10px;
display: flex;
align-items: center;
}

View file

@ -10,7 +10,7 @@
position: static;
transform: none !important;
align-items: center;
visibility: visible !important;
overflow-x: auto;
}
&--opened {
@ -20,6 +20,7 @@
&__button {
@apply --toolbox-button;
flex-shrink: 0;
}
&__tooltip {
@ -27,7 +28,6 @@
top: 25px;
padding: 6px 10px;
border-radius: 5px;
line-height: 21px;
opacity: 0;
background: var(--bg-light);
box-shadow: 0 10px 12px -9px rgba(26, 39, 54, 0.32), 0 3px 2px -2px rgba(33, 48, 73, 0.05);
@ -41,6 +41,10 @@
letter-spacing: 0.02em;
line-height: 1em;
@media (--mobile) {
display: none;
}
&-shortcut {
color: rgba(100, 105, 122, 0.6);
word-spacing: -2px;

View file

@ -60,6 +60,10 @@
border-radius: 4px;
z-index: 2;
@media (--mobile){
box-shadow: 0 5px 9px -5px rgba(21, 40, 54, 0.49),6px 15px 34px -6px rgba(33, 48, 73, 0.54);
}
&::before {
content: '';
width: 15px;
@ -72,6 +76,21 @@
background-color: #fff;
z-index: -1;
}
&--left-oriented {
&::before {
left: 15px;
margin-left: 0;
}
}
&--right-oriented {
&::before {
left: auto;
right: 15px;
margin-left: 0;
}
}
};
/**
@ -106,6 +125,7 @@
width: 34px;
height: 34px;
line-height: 34px;
padding: 0 !important;
text-align: center;
border-radius: 3px;
cursor: pointer;

990
yarn.lock

File diff suppressed because it is too large Load diff