Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: cursor under the rich text editor as text is getting erased. #22

Merged
merged 5 commits into from
Dec 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
/node_modules
/.idea
/.project
/.settings
/package-lock.json
106 changes: 106 additions & 0 deletions content-height-util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import once from 'lodash-es/once.js';

/**
* A Utility to identify the content height for an HTML content. It's used to compute the required
* height for the editor when auto-height feature is enabled.
*
* ## How it works?
* - Creates an extra TextEditor, which is rendered off-screen. (out of the screen viewable area)
* - When content height needs to be find, that TextEditor's width is set to the desired width
* and then the value (HTML) is set into it. It's height (considering scroll-height too) is the
* content-height.
* - This Extra TextEditor is rendered/initialized in early stage. So, extra time isn't spent
* at the time of finding the content-height.
*
* ## Why the text-editor being auto-resized can be used to compute the content height?
* To find the content-height for a text-editor, algorith applied is as follows:
* - Set it's initial height to the minimum height (1 line height).
* - Set the content
* - Compute the content height based on the editor's height & scrollheight.
*
* If we run the same algorithm on the live editor (where user is editing too), this provides
* false result, and user-experience issue too. This falsle result is specifically in the case
* when user is erasing the content frequently (backspace key is kept pressed).
*/


/**
* Initial width of the TextEditor in pixels.
* This width is actually of no use (in practical), because each time the content-height is
* to be computed actual width of the TextEditor is set on this editor first.
*/
const INIT_WIDTH = 500;


/**
* An instance of the Squire editor which is retrieved from the `iframe.contentWindow.editor`.
*/
let textEditor;

/**
* An instance of the iframe body which is retrieved from the `iframe.contentWindow.body`.
*/
let textContent;

/**
* A function used to create/initialize the extra text-editor, which is used to compute the
* content-height later.
*
* It can be invoked multiple times, but actual work is done only on the first invocation
* (when text-editor isn't initialized).
*
* Usage:
* It is invoked by the TextEditor when it's first rendered.
*
* @returns {Promise} Resolved when TextEditor is initialized.
*
*/
export const init = once((iframePath) => {
let resolve;
let promise = new Promise((res) => { resolve = res });
let iframe = document.createElement('iframe');
iframe.addEventListener('load', () => {
textEditor = iframe.contentWindow.editor;
textContent = iframe.contentDocument.body;
textContent.style.width = INIT_WIDTH + 'px';
resolve({textEditor, textContent});
});

iframe.style.position = 'fixed';
iframe.style.top = '-99999px';
iframe.style.left = '-99999px';
document.body.appendChild(iframe);
iframe.src = iframePath;
return promise;
});

/**
* Identifies the content-height for the given HTML.
*
* @param {*} html HTML content for which content-height is to be identified.
* @param {Number} width Width of the TextEditor for which content-height is to be identified.
* It's important configuration, because the content height is properly
* computed only when the width of the editor is properly set.
* @param {Number} height height of iframe. Default value is 150.
* Because every browser has different height for iframe so,
* we can set's a iframe height, to get proper content height.
* @returns {Number} Required content height.
*/
export const getContentHeight = (html, width, height = 150) => {
if (!html) {
return 0;
}

if (!textEditor) {
throw new Error("textEditor isn't yet initialized. Please invoked `init()` before this.");
}

if (!width) {
throw new Error("width is mandatory arguments.");
}

textContent.style.width = width + 'px';
textContent.style.height = height + 'px';
textEditor.setHTML(html);
return textContent.scrollHeight;
}
26 changes: 23 additions & 3 deletions dw-text-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { html, css } from 'lit-element';
import { LitElement } from '@dreamworld/pwa-helpers/lit-element.js';
import '@dreamworld/dw-icon/dw-icon';
import { scrollIntoView } from '@dreamworld/web-util/scrollIntoView';
import * as contentHeightUtil from './content-height-util.js';
/**
* It is a HTML5 rich text editor.
*
Expand Down Expand Up @@ -173,6 +174,13 @@ class DwTextEditor extends LitElement {
type: Boolean
},

/**
* Input property.
* Passed scroll element for show content into view.
* Default scrolling element is iframe content.
*/
scrollingElement: { type: Object },

/**
* Current state of Bold menu in toolbar.
*/
Expand Down Expand Up @@ -283,6 +291,11 @@ class DwTextEditor extends LitElement {
this.autoFocus = false;
this.iframePath = '/squire.html';
this.minHeight = 150; // Minumun height for edit mode. Default is 150px;

/**
* content height util's is ready or not.
*/
this._contentHeightUtilReady;
}

updated(changedProperties) {
Expand Down Expand Up @@ -356,7 +369,8 @@ class DwTextEditor extends LitElement {
//Old height
const oldHeight = this._iframe.style.height;
const minHeight = this.readonly ? 0 : this.minHeight;
const scrollHeight = Math.max(this.content.scrollHeight, minHeight);
const contentHeight = this._contentHeightUtilReady ? contentHeightUtil.getContentHeight(this.getValue(), this.content.offsetWidth) : this.content.scrollHeight;
const scrollHeight = Math.max(contentHeight, minHeight);

//Sets iframe Height to content height & fires height changed event if iFrame height is changed
if (oldHeight !== `${scrollHeight}px`) {
Expand Down Expand Up @@ -388,14 +402,20 @@ class DwTextEditor extends LitElement {
this._iframe = this.shadowRoot.querySelector('iframe');
this._editor = this._iframe.contentWindow.editor;
this.content = this._iframe.contentDocument.body;
this.content.style.overflow = 'hidden';
this.content.style.overflow = 'hidden';
this.scrollingElement = this.scrollingElement || this.content;
this._updateReadOnly();
this.setValue(this.value);

//Set focus if `autoFocus` is true
if(this.autoFocus) {
this._editor.focus();
}

//Initialize dummy text editor for get content height;
contentHeightUtil.init(this.iframePath).then(() => {
this._contentHeightUtilReady = true;
});

this.content.addEventListener('click', this._dispatchBodyTapEvent.bind(this));
this._editor.addEventListener('pathChange', this._pathChanged.bind(this));
Expand Down Expand Up @@ -591,7 +611,7 @@ class DwTextEditor extends LitElement {
const win = doc.defaultView || doc.parentWindow;
const focusNode = win.getSelection().focusNode;
if (focusNode && focusNode.nodeType == 1) {
scrollIntoView(this.content, focusNode);
scrollIntoView(this.scrollingElement, focusNode);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"@dreamworld/pwa-helpers": "^1.8.0",
"@dreamworld/web-util": "^1.0.0",
"@webcomponents/webcomponentsjs": "2.2.10",
"lit-element": "2.2.1"
"lit-element": "2.2.1",
"lodash-es": "^4.17.15"
},
"devDependencies": {
"semantic-release": "17.0.8"
Expand Down
Loading