I followed a SO question on how to create a placeholder for div[contenteditable].
My code looks like: http://jsfiddle.net/sdxjgkzm/
$('div[data-placeholder]').on 'keydown input', ->
if (this.textContent)
this.dataset.divPlaceholderContent = 'true'
else
delete(this.dataset.divPlaceholderContent)
Unfortunately, the problem is that as you can see the standard input's placeholder stays until you begin typing, while the contenteditable's goes away as soon as you click inside.
How do I fix this?
change your html a bit then use the below css:use placeholder instead of data-placeholder i.e. without data attribute.
input,div {
border: 1px black solid;
margin-top: 20px;
}
[contenteditable=true]:empty:before {
content: attr(placeholder);
}
<input placeholder="test"/>
<div contenteditable='true' placeholder="test"></div>
Check this out, CSS only :)
Placeholder support for contentEditable elements, without JavaScript
Updated Fiddle: enter link description here
All you need is to add the following CSS:
[contenteditable=true]:empty:before {
content: attr(placeholder);
display: block; /* For Firefox */
}
/* General Styling for Demo only */
div[contenteditable=true] {
border: 1px dashed #AAA;
width: 290px;
padding: 5px;
}
pre {
background: #EEE;
padding: 5px;
width: 290px;
}
<h3>Placeholder support for contentEditable elements,<br>without JavaScript!</h3>
<h5>Demo:</h5>
<div contenteditable="true" placeholder="Enter text here..."></div>
<p>All you need is to add the following CSS:</p>
<pre>
[contenteditable=true]:empty:before {
content: attr(placeholder);
display: block; /* For Firefox */
}
</pre>
<h5>Notes</h5>
<ul>
<li>Can add a different style than actual text like opacity, italic, etc</li>
<li>If your html needs to be 100% compliant, you can replace "placeholder" for "data-placeholder" on both files</li>
<li>Chrome will add <br />'s inside contentEditable elements in some cases, breaking the :empty check. Can be fixed with a bit of JavaScript.</li>
</ul>
<i>By Ariel Flesler</i>
Related
Update
I'd modded the CSS given by David Thomas a bit. Its now a banner.
.div.popular::before {
/* setting the default styles for
the generated content: */
display: block;
width: 10em;
height: 2em;
line-height: 2em;
text-align: center;
background: #F60;
color: #fff;
font-size: 1.4rem;
position: absolute;
top: 30px;
right: 0px;
z-index: 1;
}
I would like to make a folded corner sort of like in this post: Folded banner using css
--- Original post ---
Let me first explain what I'm trying to do. I'm trying to give some post some extra attention by making a little circle with some call-to-action text in it.
But I only want this to trigger when a div has a specific class.
So if the div the class populair or sale I would like to have a little circle show up on that post. This script what I am using right now.
$(document).ready(function($){
if($("#front-page-items").hasClass('populair')){
$(".populair-div").show();
}
if($("#front-page-items").hasClass('sale')){
$(".sale-div").show();
}
});
And this HTML:
<div class="populair-div" style="display:none;">
<strong>Populair</strong>
</div>
<div class="sale-div" style="display:none;">
<strong>Sale</strong>
</div>
But this only show's the populair-div and not the other one. I'm guessing my script is wrong. Should I use else for all the other call-to-action classes?
$(document).ready(function($){
if($("#front-page-items").hasClass('populair')){
$(".populair-div").show();
}
else($("#front-page-items").hasClass('sale')){
$(".sale-div").show();
}
else($("#front-page-items").hasClass('Free')){
$(".free-div").show();
} // and so on
});
Is there someone that could help me out? Also is it possible to echo the div so I don't have to write a whole div for every call-to-action div?
For something like this, where the displayed text is explicitly linked to the class-name of the element it's easiest to use CSS and the generated content available, effectively hiding the elements you don't wish to show by default and then explicitly allowing elements you want to show, along with the generated content of those elements (using the ::before and ::after pseudo-elements:
div {
/* preventing <div> elements
from showing by default: */
display: none;
}
div.populair-div,
div.sale-div {
/* ensuring that elements matching
the selectors above (<div>
elements with either the 'sale-div'
or 'populair-div' class-names
are shown: */
display: block;
}
div.populair-div::before,
div.sale-div::before {
/* setting the default styles for
the generated content: */
display: block;
width: 4em;
height: 4em;
line-height: 4em;
text-align: center;
border: 3px solid transparent;
border-radius: 50%;
}
div.populair-div::before {
/* setting the text with the
"content" property: */
content: "Popular";
/* providing a specific colour
for the generated contents'
border: */
border-color: #0c0;
}
div.sale-div::before {
content: "Sale";
border-color: #f90;
}
/* entirely irrelevant, just so you can
see a (slightly prettified) difference
should you remove the default display
property for the <div> elements: */
code {
background-color: #ddd;
}
em {
font-style: italic;
}
<div class="neither-popular-nor-sale">
<p>
This element should not be shown, it has neither a class of <code>"populair-div"</code> <em>or</em> <code>"sale-div"</code>.
</p>
</div>
<div class="populair-div">
</div>
<div>Also not to be shown.</div>
<div class="sale-div">
</div>
You can use toggle function for this. It will be shorter and clearer.
Display or hide the matched elements.
Note: The buttons is for tests.
$(document).ready(function($){
init();
});
function init() {
$(".populair-div").toggle($("#front-page-items").hasClass('populair'));
$(".sale-div").toggle($("#front-page-items").hasClass('sale'));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="front-page-items" class="populair sale"></div>
<div class="populair-div">populair-div</div>
<div class="sale-div">sale-div</div>
<hr />
<button onclick="document.getElementById('front-page-items').classList.toggle('populair');init()">toggle populair</button>
<button onclick="document.getElementById('front-page-items').classList.toggle('sale');init()">toggle sale</button>
I developed, using ng-show and ng-hide, a box of description that pops up under a text after I click it.
But the problem is, the decription box is not shown exactly under the text, like here in the picture.
I want the description to be shown exactly under R2A.
This is my code:
HTML
<ion-content>
<div class="contenu">
<p>Salut</font><font size="4">, Welcome</font><font size="4">, Bienvenue chez </font>R2A<font size="4">...</font></p>
<div ><font class="boxed" ng-show="collapsed"size="4" >I am description</font></div>
<p> Nous sommes ton equipe de Welcomers, nous allons t'accompagner pour ta première journée.</p>
</div>
</ion-content>
CSS
.collapsed {
width: 300px;
padding: 25px;
border: 25px solid navy;
margin: 25px;
}
.contenu .boxed {
display: inline-block;
padding:20px;
border-radius: 10px;
box-shadow: 5px 5px 10px #000;
background-color: #FBC02D;
}
First, transform the a into a button and you can put your boxed element inside your link.
Now, you can position the boxed element relative to the button element which contains the R2A text.
The boxedelement should have the position: absolute;, so it positions relative to the first ancestor which does not have the position static. Next, the button, which by default has the position static, should have the position: relative;. This means that it positions relative to its default position. If we don't specify top, left etc. the position remains the same. More here.
So, the html :
...
<button class="important-link" ng-model="collapsed" ng-click="collapsed=!collapsed">
<span>R2A</span>
<div class="boxed" ng-show="collapsed">
I am Description
</div>
</button>
...
And the css:
important-link {
position: relative;
background: none;
border: none;
}
.important-link span {
font-size: 30px;
color: #B9121B;
}
.boxed {
min-width: 100px;
position: absolute;
width: auto;
top: 50px;
padding: 20px;
border-radius: 10px;
box-shadow: 5px 5px 10px #000;
background-color: #FBC02D;
}
And here is a fiddle with the whole example.
PS. It's recommended not to use inline CSS in your HTML like style="font-size:180%; ..
Edit
So, if you don't want some kind of 'tooltip' effect there are two solutions.
In the first one, you add a margin-top to the next div, so that the description box doesn't overlap the text below. You can add it with ng-class on ng-style so the margin applies only if the description box is shown.
Second solution, inside the button element, both the text (R2A) and the description should be block elements, so that they position one under the other (block vs inline elements).
And then you remove the position: absolute from the boxed element (and you can also remove the position : relative; from the parent).
Here is the updated fiddle.
There are still some limitations in both solutions, so the one you choose depends on the final result.
You can also use JS to manipulate and position the elements of the DOM.
Just put both of these elements in a separate div element and add a break in between <br>:
<div>R2A
<font size="4">...</font></p>
<br>
<div>
<font class="boxed" ng-show="collapsed"size="4" >I am description</font>
</div>
</div>
Or put them in the same span.
You may have to fiddle around with the margins as well.
Sorry I can't provide anymore information on the issue other than a screenshot, I've currently got no way to test on Windows with Chrome.
This is what the placeholders look like on Win7 Chrome:
Some relevant styles:
.field input, .field textarea {
width: 100%;
}
input, textarea {
background: #fbfbfb;
padding: 15px;
}
input {
line-height: normal;
}
button, input, optgroup, select, textarea {
color: inherit;
font: inherit;
margin: 0;
border: none;
}
label, input {
display: block;
}
HTML:
<div class="field">
<input id="cf-name" type="text" name="name" value="" placeholder="Name" autocomplete="off">
</div>
The page is here:
http://dev.metertech.co.uk/contact-us
Anyone faced this issue and know what's going on?
You have set the line-height for the inputs to be normal, but if you look at your placeholder pseudo-classes, you set them to be line-height: 2. So your placeholder text is trying to occupy more height than the actual height of your inputs, hence the "chopped off" placeholder text.
To fix it, remove the line-height property from the placeholders.
You probably also don't need line-height:normal - it is the default.
Here is your fix
input {
line-height: 28px;
}
When you open this page (see Live demo) with Chrome :
<span id="myspan" contenteditable=true></span>
CSS :
#myspan { border: 0; outline: 0;}
JS :
$(myspan).focus();
the contenteditable span has focus (you can start to write things and you will see that it already had focus), but we don't see the "I" edit cursor.
How to make that this cursor is displayed ? (Remark : outline:0 is needed, as well as the fact that the span is empty even with no white space).
Note : With Firefox, the cursor is displayed.
The problem is that spans are inline elements. Just add display:block; to your CSS and it will fix the problem.
$(myspan).focus();
#myspan {
border: 0;
outline: 0;
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<span id="myspan" contenteditable=true></span>
I added padding to the left and the cursor appears.
#myspan
{
border: 0;
outline: 0;
min-width: 100px;
height: 30px;
padding-left: 1px;
}
Demo in jsFiddle
.cont_edit {
outline: 1px solid transparent;
}
This just has to do with the way an empty ContentEditable area is rendered. To prove it's not about the focus, add some text to your editable div, and then delete it. When the last character is gone, the cursor will disappear
From the question Setting the caret position to an empty node inside a contentEditable element
The selection/range model is based around indexes into text content, disregarding element boundaries. I believe it may be impossible to set the input focus inside an inline element with no text in it. Certainly with your example I cannot set focus inside the last element by clicking or arrow keys.
It almost works if you set each span to display: block, though there's still some highly strange behaviour, dependent on the existence of whitespace in the parent. Hacking the display to look inline with tricks like float, inline-block and absolute position make IE treat each element as a separate editing box. Relative-positioned block elements next to each other work, but that's probably impractical.
You could also try adding a zero-width character like
document.getElementById('myspan').focus();
#myspan {
border: 0;
outline: 0;
}
<span id="myspan" contenteditable="true"></span>
The solution was to change <span> to <div> (I've seen that this solves many contenteditable problems in other questions here and there) + to add a min-width.
Indeed, with the following code, the size of the <div> would be 0px x 18px ! That explains why the caret (edit cursor) would be hidden !
HTML
<div id="blah" contenteditable=true></div>
CSS
#blah {
outline: 0;
position: absolute;
top:10px;
left:10px;
}
JS
$("#blah").focus();
Then, adding
min-width: 2px;
in the CSS will allow the caret to be displayed, even with Chrome : http://jsfiddle.net/38e9mkf4/2/
The issue I faced on Chrome v89.0.4389.90 was that contenteditable fields would sometimes show the blinking caret on focusin and sometimes not. I noticed it always blinks when there's already content in the field before focusing. It's when there's no content that the sometimes will/won't behavior occurs.
At first, I thought there must be some conflicting event handler that's erratically taking focus away. I disabled all my event binds and timers. Still the same erratic behavior. Then I thought it might be some conflicting CSS, so I disabled all stylesheets. At least now the behavior was consistent: the caret blinks 100% of the time when the field has content; the caret does not blink 100% of the time when the field has no content.
I enabled binds and stylesheets again. My div was already set to display: block; with min-width, min-height, and padding set in the final computed style set. None of the other answers here worked. I do have a placeholder on :empty:before that was a possible culprit. I commented that out. Now the behavior was consistent again, same as if the stylesheet was off. Oddly enough, the runnable snippet on SO works with the same computed CSS stack. I want to keep the placeholder, so it requires further research with my actual codebase...
The only solution I could get to work 100% of the time with my current issue involved forcibly placing the caret inside empty fields by creating a blank space and removing it immediately afterwards. Best I can do for a workaround until debugging the root cause.
//force caret to blink inside masks
let force_caret = function() {
if (!this.textContent) {
this.textContent = ' ';
let r = document.createRange(),
s = window.getSelection();
r.setStart(this.childNodes[0], 0);
r.collapse(true);
s.removeAllRanges();
s.addRange(r);
this.textContent = '';
}
}
//binds
let els = document.querySelectorAll("[contenteditable]");
for (let i = 0; i < els.length; i++) {
els[i].addEventListener('focusin', force_caret, false);
}
/* styles irrelevant to the issue, added for visual assist */
:root {
--b-soft: 1px solid silver;
--bs-in: inset 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px rgba(255, 255, 255, 0.1);
--c-soft: gray;
--lg-warm: linear-gradient(30deg, rgb(254, 250, 250), #eedddd);
}
body {
font-family: system-ui, -apple-system, -apple-system-font, 'Segoe UI', 'Roboto', sans-serif;
}
[contenteditable] {
outline: initial;
}
[contenteditable][placeholder]:empty:before {
content: attr(placeholder);
color: var(--c-soft);
background-color: transparent;
font-style: italic;
opacity: .5;
font-size: .9em;
}
.input {
border-bottom: var(--b-soft);
padding: .2em .5em;
}
.input_mask {
display: flex;
align-items: baseline;
color: var(--c-soft);
}
.mask {
box-shadow: var(--bs-in);
border-radius: .2em;
background: var(--lg-warm);
font-weight: 500;
border: 1px solid transparent;
text-transform: uppercase;
/* styles possibly relevant to the issue according to other proposed solutions */
margin: 0 .4em .1em .4em;
padding: .2em .4em;
min-width: 3em;
min-height: 1em;
text-align: center;
}
<div data-type="tel" data-id="phone" class="input input_mask">
<span>+1 (</span>
<div maxlength="3" contenteditable="true" placeholder="111" class="mask"></div>
<span>)</span>
<div maxlength="3" contenteditable="true" placeholder="111" class="mask"></div>
<span>-</span>
<div maxlength="4" contenteditable="true" placeholder="1111" class="mask"></div>
<span>x</span>
<div maxlength="5" contenteditable="true" class="mask"></div>
</div>
Add a CSS style of
min-height: 15px;
you may also need
display: block;
to your contenteditable="true" tag
For me setting it content of contenteditable div to <br> works. I tried setting it to nbsp; but that creates extra character space in the div before i start editing. So, i choose this:
<div id="blah" contenteditable=true><br></div>
over:
<div id="blah" contenteditable=true>nbsp;</div>
Hope this helps.
I use Chrome and your Code works fine.
Try to use cursor: text; in your CSS. See here
For an application we're developing at the company where I work, we need an input that supports inserting emoticons inside our JS-based web app. We're currently using an input with the emoticon shortcodes (ie ':-)') and would like to switch to inserting actual, graphical images.
Our original plan was to use a contenteditable <div>. We're using listeners for the paste event as well as the different key/mouse interactions to ensure no unwanted markup enters the contenteditable (we strip text out of its container tags and leave only image tags that we inserted ourselves).
However, the problem right now is that the div resizes if you put in enough content (ie its height increases). We don't want this to happen, nor is it acceptable for the text to just be hidden (ie plain overflow: hidden). So:
Is there a way to make the contenteditable div behave like a single-line input?
I'd like it best if there is a relatively simple attribute/css property that I've missed that will do what I want, but if necessary CSS+JS suggestions will also be appreciated.
[contenteditable="true"].single-line {
white-space: nowrap;
width:200px;
overflow: hidden;
}
[contenteditable="true"].single-line br {
display:none;
}
[contenteditable="true"].single-line * {
display:inline;
white-space:nowrap;
}
<div contenteditable="true" class="single-line">
This should work.
</div>
Other answers are wrong and contain few mistakes (on 2019-05-07). Other solutions suggest to use "white-space: nowrap" (prevents carrying to another line) + "overflow: hidden" (prevents long text going beyond the field) + hiding <br> and other.
First mistake in that solutions is "overflow: hidden" also prevents scrolling the text. User will not be able to scroll the text by:
Pressing mouse middle button
Selecting the text and moving mouse pointer to the left or right
Using horizontal mouse scroll (when user have such a thing)
The only way he can scroll is using keyboard arrows.
You can solve this problem by using "overflow: hidden" and "overflow: auto" (or "scroll") at the same time. You should create parent div with "overflow: hidden" to hide content user should not see. This element must have input borders and other design. And you should create child div with "overflow-x: auto" and "contenteditable" attribute. This element will have scrollbar so user can scroll it without any limitations and he will not see this scrollbar because of hiding overflow in parent element.
Example of solution:
document.querySelectorAll('.CETextInput').forEach(el => {
//Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
el.parentNode.addEventListener('mousedown', function(e) {
if (e.target === this) {
setTimeout(() => this.children[0].focus(), 0);
}
});
//Prevent Enter. See purpose in "Step 2" in answer.
el.parentNode.addEventListener('keydown', function(e) {
if (e.keyCode === 13)
e.preventDefault();
});
});
.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
display: inline-block;
border: 1px solid #aaa;
}
.CETextInputCont {
overflow: hidden;
cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
/*Style:*/
width: 10em;
height: 1em;
line-height: 1em;
padding: 5px;
font-size: 20px;
font-family: sans-serif;
}
.CETextInput {
white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
overflow-x: auto;
min-height: 100%; /*to prevent zero-height with no text*/
/*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
padding: 5px 0;
margin-top: -5px;
outline: none; /*Prevent border on focus in some browsers*/
}
<div class="CETextInputBorder">
<div class="CETextInputCont">
<div class="CETextInput" contenteditable></div>
</div>
</div>
Step 2: Solving problem with <br> and other:
Also there is a problem that user or extensions can paste
<br> (can be pasted by user)
<img> (may have big size) (can be pasted by user)
elements with another "white-space" value
<div> and other elements that carry text to another line
elements with unsuitable "display" value
But advise to hide all <br> is wrong too. That is because Mozilla Firefox adds <br> element to empty field (I guess it may be workaround of bug with text cursor disappearing after deleting last character; checked in Firefox 66 released on 2019-03-19). If you hide this element then when user moves focus to field caret will be set in this hidden <br> element and text cursor will be hidden too (always).
You can fix this if you will be <br> when you know field is empty. You need some javascript here (you cannot use :empty selector because field contains <br> elements and not empty). Example of solution:
document.querySelectorAll('.CETextInput').forEach(el => {
//OLD CODE:
//Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
el.parentNode.addEventListener('mousedown', function(e) {
if (e.target === this) {
setTimeout(() => this.children[0].focus(), 0);
}
});
//Prevent Enter to prevent blur on Enter
el.parentNode.addEventListener('keydown', function(e) {
if (e.keyCode === 13)
e.preventDefault();
});
//NEW CODE:
//Update "empty" class on all "CETextInput" elements:
updateEmpty.call(el); //init
el.addEventListener('input', updateEmpty);
function updateEmpty(e) {
const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
this.classList.toggle('empty', !s);
}
});
/*OLD CODE:*/
.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
display: inline-block;
border: 1px solid #aaa;
}
.CETextInputCont {
overflow: hidden;
cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
/*Style:*/
width: 10em;
height: 1em;
line-height: 1em;
padding: 5px;
font-size: 20px;
font-family: sans-serif;
}
.CETextInput {
white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
overflow-x: auto;
min-height: 100%; /*to prevent zero-height with no text*/
/*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
padding: 5px 0;
margin-top: -5px;
outline: none; /*Prevent border on focus in some browsers*/
}
/*NEW CODE:*/
.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
display: none;
}
.CETextInput * {
display: inline;
white-space: pre;
}
<!--OLD CODE:-->
<div class="CETextInputBorder">
<div class="CETextInputCont">
<div class="CETextInput" contenteditable></div>
</div>
</div>
Step 3: Solving problem with getting value:
We hided <br> elements so "innerText" value will not contain them. But:
When "empty" class is set result may contain <br> elements.
Your other styles or extensions may override "display: none" by "!important" mark or by rule with higher priority.
So when you get value you should make replace to avoid accidental getting line breaks:
s = s.replace(/[\r\n]+/g, '');
Do not use javascript for hiding <br>
Also you could solve the problem with <br> by removing them by javascript but this is very bad solution because after every removing user cannot use "undo" action anymore for canceling changes was made before removing.
Also you could use document.execCommand('delete') to delete <br> but it is hard to implement + user can undo your deletion and restore <br> elements.
Adding placeholder
It was not asked in question but I guess many people using single-line contenteditable elements will need it. Here is example how to make placeholder using css and "empty" class we talked above:
//OLD CODE:
document.querySelectorAll('.CETextInput').forEach(el => {
//Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
el.parentNode.addEventListener('mousedown', function(e) {
if (e.target === this) {
setTimeout(() => this.children[0].focus(), 0);
}
});
//Prevent Enter to prevent blur on Enter
el.parentNode.addEventListener('keydown', function(e) {
if (e.keyCode === 13)
e.preventDefault();
});
//Update "empty" class on all "CETextInput" elements:
updateEmpty.call(el); //init
el.addEventListener('input', updateEmpty);
function updateEmpty(e) {
const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
this.classList.toggle('empty', !s);
//NEW CODE:
//Make element always have <br>. See description in html. I guess it is not needed because only Firefox has bug with bad cursor position but Firefox always adds this element by itself except on init. But on init we are adding it by ourselves (see html).
if (!s && !Array.prototype.filter.call(this.children, el => el.nodeName === 'BR').length)
this.appendChild(document.createElement('br'));
}
});
/*OLD CODE:*/
.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
display: inline-block;
border: 1px solid #aaa;
}
.CETextInputCont {
overflow: hidden;
cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
/*Style:*/
width: 10em;
height: 1em;
line-height: 1em;
padding: 5px;
font-size: 20px;
font-family: sans-serif;
}
.CETextInput {
white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
overflow-x: auto;
min-height: 100%; /*to prevent zero-height with no text*/
/*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
padding: 5px 0;
margin-top: -5px;
outline: none; /*Prevent border on focus in some browsers*/
}
.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
display: none;
}
.CETextInput * {
display: inline;
white-space: pre;
}
/*NEW CODE:*/
.CETextInput[placeholder].empty::before { /*Use ::before not ::after or you will have problems width first <br>*/
content: attr(placeholder);
display: inline-block;
width: 0;
white-space: nowrap;
pointer-events: none;
cursor: text;
color: #b7b7b7;
padding-top: 8px;
margin-top: -8px;
}
<!--OLD CODE:-->
<div class="CETextInputBorder">
<div class="CETextInputCont">
<div class="CETextInput" placeholder="Type something here" contenteditable><br></div>
</div>
</div>
<!--We manually added <br> element for Firefox browser because Firefox (tested on 2019-05-11, Firefox 66) has bug with bad text cursor position in empty contenteditable elements that have ::before or ::after pseudo-elements.-->
Solution with only one div and "scrollbar-width"
You can also use only one div by setting "overflow-x: auto", "overflow-y: hidden" and "scrollbar-width: none". But "scrollbar-width" is new property and works only in Firefox 64+ and no other browsers yet.
You can also add:
webkit-prefixed version: "-webkit-scrollbar-width: none"
non-standardized ".CETextInput::-webkit-scrollbar { display: none; }" (for webkit-based browsers)
"-ms-overflow-style: none"
I would not recommend to use this solution, but here is example:
//OLD CODE:
document.querySelectorAll('.CETextInput').forEach(el => {
//Focusing on child is not needed anymore
//Prevent Enter to prevent blur on Enter
el.addEventListener('keydown', function(e) {
if (e.keyCode === 13)
e.preventDefault();
});
//Update "empty" class on all "CETextInput" elements:
updateEmpty.call(el); //init
el.addEventListener('input', updateEmpty);
function updateEmpty(e) {
const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
this.classList.toggle('empty', !s);
}
});
/*NEW CODE:*/
.CETextInput {
white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
overflow-x: auto; /*or "scroll"*/
overflow-y: hidden;
-webkit-scrollbar-width: none; /*Chrome 4+ (probably), webkit based*/
scrollbar-width: none; /*FF 64+, Chrome ??+, webkit based, Edge ??+*/
-ms-overflow-style: none; /*IE ??*/
/*Style:*/
width: 10em;
height: 1em;
line-height: 1em;
padding: 5px;
border: 1px solid #aaa;
font-size: 20px;
font-family: sans-serif;
}
.CETextInput::-webkit-scrollbar {
display: none; /*Chrome ??, webkit based*/
}
/*OLD CODE:*/
.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
display: none;
}
.CETextInput * {
display: inline;
white-space: pre;
}
<!--NEW CODE:-->
<div class="CETextInput" contenteditable></div>
This solution has 3 problems with paddings:
In Firefox (tested on 2019-05-11, Firefox 66) there is no right padding when long text is typed. That is because Firefox does not display bottom or right padding when using padding in the same element that have scrollbar and when content is scrolled to the end.
In all browsers there is no padding when scrolling long text in middle position. It looks worse. <input type="text"> does not have this problem.
When user press home or end browsers scroll to place paddings are not visible.
To solve these problems you need use 3 elements like we used before but in this case you don't need use scrollbar-width. Our solution with 3 elements does not have these problems.
Other problems (in every solution):
Blur on pasting text ends with line break. I will think how to fix it.
When using paddings this.children[0].focus() is not enough in webkit-based browsers (cursor position is not where user clicked). I will think how to fix it.
Firefox (tested on 2019-05-11, Firefox 66): When short text is typed user cannot select last word by double clicking on the right of it. I will think about it.
When user starts text selection in the page he can end it in our field. Usual <input type="text"> does not have this behavior. But I don't think it is critical.
I think you are looking for a contenteditable div with only one line of text that scrolls horizontally when it overflows the div. This should do the trick: http://jsfiddle.net/F6C9T/1
div {
font-family: Arial;
font-size: 18px;
min-height: 40px;
width: 300px;
border: 1px solid red;
overflow: hidden;
white-space: nowrap;
}
<div contenteditable>
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
</div>
The min-height: 40px incorporates the height for when the horizontal scroll bar appears. A min-height:20px would automatically expand when the horizontal scroll bar appears, but this doesn't work in IE7 (though you could use conditional comments to apply separate styling if you wanted it).
I adapted the #tw16 accepted solution (on 5th Dec 2019) to add in scrolling. The trick was to add scrolling using overflow-x: auto but then hide the scrollbar (https://stackoverflow.com/a/49278385)
/* Existing Solution */
[contenteditable="true"].single-line {
white-space: nowrap;
width: 200px;
overflow: hidden;
}
[contenteditable="true"].single-line br {
display:none;
}
[contenteditable="true"].single-line * {
display:inline;
white-space:nowrap;
}
/* Make Scrollable */
[contenteditable="true"].single-line {
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* Internet Explorer 10+ */
}
[contenteditable="true"].single-line::-webkit-scrollbar { /* WebKit */
width: 0;
height: 0;
}
<div contenteditable="true" class="single-line">
This should scroll when you have really long text!
</div>
Here's a relatively simple solution that uses the contenteditable's input event to scan the dom and filter out various flavors of new lines (so it should be robust against copy/paste, drag 'n drop, hitting enter on the keyboard, etc). It condenses multiple TextNodes into single TextNodes, strips newlines from TextNodes, kills BRs, and puts a "display: inline" on any other element that it touches. Tested on Chrome, no guarantees anywhere else.
var editable = $('#editable')
editable.on('input', function() {
return filter_newlines(editable);
});
function filter_newlines(div) {
var node, prev, _i, _len, _ref, _results;
prev = null;
_ref = div.contents();
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = _ref[_i];
if (node.nodeType === 3) {
node.nodeValue = node.nodeValue.replace('\n', '');
if (prev) {
node.nodeValue = prev.nodeValue + node.nodeValue;
$(prev).remove();
}
_results.push(prev = node);
} else if (node.tagName.toLowerCase() === 'br') {
_results.push($(node).remove());
} else {
$(node).css('display', 'inline');
filter_newlines($(node));
_results.push(prev = null);
}
}
return _results;
}
#editable {
width: 200px;
height: 20px;
border: 1px solid black;
}
<div id="editable" contenteditable="true"></div>
Or here's the fiddle: http://jsfiddle.net/tG9Qa/
If you want a different way of solving it other than changing the requirements, with a little display:table it is fully possible =)
.container1 {
height:20px;
width:273px;
overflow:hidden;
border:1px solid green;
}
.container2 {
display:table;
}
.textarea {
width:273px;
font-size: 18px;
font-weight: normal;
line-height: 18px;
outline: none;
display: table-cell;
position: relative;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
word-wrap: break-word;
overflow:hidden;
}
<div class="container1">
<div class="container2">
<div contenteditable="true" class="textarea"></div>
</div>
</div>
So, for posterity: the simplest solution is to get your product manager to change the requirements so you can do multiline editing. This is what ended up happening in our case.
However, before that happened, I ended up going quite a way in creating a manually moving single-line richtext editor. I wrapped it up in a jQuery plugin in the end. I don't have time to finish it up (there are probably bugs in IE, Firefox works best and Chrome works pretty well - comments are sparse and sometimes not very clear). It uses parts of the Rangy library (extracted because I didn't want to rely on the complete library) to get screen positions of selections in order to test for mouse position vs. selection (so you can drag selections and move the box).
Roughly, it works by using 3 elements. An outer div (the thing you call the plugin on), which gets overflow: hidden, and then 2 levels of nesting inside it. The first level is absolutely positioned, the second level is the actual contenteditable. This separation is necessary because otherwise some browsers will give the contenteditable absolutely positioned element grippies, to let the user move it around...
In any case, then there is a whole bunch of code to move the absolutely positioned element around inside the top element, thus moving the actual contenteditable. The contenteditable itself has white-space nowrap, etc. etc. to force it to stay a single line.
There is also code in the plugin that strips out anything that isn't an image (like br, tables, etc. etc.) from any text that's pasted / dragged into the box. You need some parts of this (like the brs, stripping/normalizing paragraphs, etc.) but maybe you would normally want to keep links, em, strong and/or some other formatting.
Source: https://gist.github.com/1161922
Check out this answer I just posted. This should help you out:
How to create a HTML5 single line contentEditable tab which listens to Enter and Esc
Here is the HTML markup:
<span contenteditable="false"></span>
Here is the jQuery/javascript:
$(document).ready(function() {
$('[contenteditable]').dblclick(function() {
$(this).attr('contenteditable', 'true');
clearSelection();
$(this).trigger('focus');
});
$('[contenteditable]').live('focus', function() {
before = $(this).text();
if($(this).attr('contenteditable') == "true") { $(this).css('border', '1px solid #ffd646'); }
//}).live('paste', function() {
}).live('blur', function() {
$(this).attr('contenteditable', 'false');
$(this).css('border', '1px solid #fafafa');
$(this).text($(this).text().replace(/(\r\n|\n|\r)/gm,""));
if (before != $(this).text()) { $(this).trigger('change'); }
}).live('keyup', function(event) {
// ESC=27, Enter=13
if (event.which == 27) {
$(this).text(before);
$(this).trigger('blur');
} else if (event.which == 13) {
$(this).trigger('blur');
}
});
$('[contenteditable]').live('change', function() {
var $thisText = $(this).text();
//Do something with the new value.
});
});
function clearSelection() {
if ( document.selection ) {
document.selection.empty();
} else if ( window.getSelection ) {
window.getSelection().removeAllRanges();
}
}
Hope this helps someone!!!
You can replace this div with text input (after onclick event is called).
I have used something similar to this plugin and it worked fine.
with jQuery I have set a .keypress event and then tests for e.keyCode == 13 (return key) and if is return false from the event and the editing is not able to make multilines
$('*[contenteditable=yes]').keypress(function(e) {
if(e.keyCode == 13 && !$(this).data('multiline')) {
return false;
}
})