With draft-js and a styled component, I made an inline input intended for a calculator. When I type into it with a keyboard, it works as expected:
When I press the plus button, a "+" is added to the text, but the view doesn't scroll:
Here's the behavior and the code in a codesandbox:
https://codesandbox.io/s/charming-brattain-mvkj3?from-embed=&file=/src/index.js
How can I get the view to scroll when text is added programmatically like that?
At long last, I figured out a solution. I added this to the calculator input component:
const shell = React.useRef(null);
const [scrollToggle, setScrollToggle] = React.useState(
{ value: false }
);
React.useEffect(() => {
const scrollMax = shell.current.scrollWidth - shell.current.clientWidth;
shell.current.scrollLeft += scrollMax;
}, [scrollToggle]);
const scrollToEnd = () => {
setScrollToggle({ value: !scrollToggle.value });
};
Then at the end of the insertChars function I added scrollToEnd();.
And I set ref={shell} on <InputShell>
Related
I am having an issue with MDC web textfield component. The issue is that the outline does not stay when I click on the textfield to type in an input. See below for the pug and javascript code :
div.mdc-layout-grid__cell--span-4.mdc-layout-grid__cell--span-12-phone.mdc-layout-grid__cell--span-12-tablet
label.mdc-text-field.mdc-text-field--filled
span.mdc-text-field__ripple
span.mdc-floating-label(id="first_name") First Name :
input.mdc-text-field__input(type="text" aria-labelledby="first_name")
span.mdc-line-ripple
div.mdc-layout-grid__cell--span-4.mdc-layout-grid__cell--span-12-phone.mdc-layout-grid__cell--span-12-tablet
label.mdc-text-field.mdc-text-field--outlined
span.mdc-notched-outline
span.mdc-notched-outline__leading
span.mdc-notched-outline__notch
span.mdc-floating-label(id="last_name") Surname :
span.mdc-notched-outline__trailing
input.mdc-text-field__input(type="text" aria-labelledby="last_name")
const textFields = [].map.call(document.querySelectorAll('.mdc-text-field'), function (el) {
return new mdc.textField.MDCTextField(el);
});
const notchedOutline = [].map.call(document.querySelectorAll('.mdc-notched-outline'), function (el) {
return new mdc.notchedOutline.MDCNotchedOutline(el);
});
const floatingLabel = [].map.call(document.querySelectorAll('.mdc-floating-label'), function (el) {
return new mdc.floatingLabel.MDCFloatingLabel(el);
});
const lineRipple = [].map.call(document.querySelectorAll('.mdc-line-ripple'), function (el) {
return new mdc.lineRipple.MDCLineRipple(el);
});
```[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/E8t9g.png
The primary color of the website was white and this made it look as though it disappeared when it was actually just white. When you change the primary color of the website, it reflects on the textfield as well.
I'm using Prismjs in my React app and everything's working fine. I just have a caret display lag whenever I move it using my keyboard arrows. In standard inputs/textareas, it looks like the caret sort of "resets" its cycle when moved. But here, it just keeps on blinking at the same pace. So if you press left arrow four times in a row, you'll have to wait until the caret appears again to see your current position (which lasts something like 0.5 second). Which is kind of disturbing.
Here's my code if it helps :
import { useState, useEffect } from "react";
import Prism from "prismjs";
export default function EditCode() {
const [content, setContent] = useState("");
const handleKeyDown = (e) => {
let value = content,
selStartPos = e.currentTarget.selectionStart;
if (e.key === "Tab") {
value =
value.substring(0, selStartPos) +
" " +
value.substring(selStartPos, value.length);
e.currentTarget.selectionStart = selStartPos + 3;
e.currentTarget.selectionEnd = selStartPos + 4;
e.preventDefault();
setContent(value);
}
};
useEffect(() => {
Prism.highlightAll();
}, []);
useEffect(() => {
Prism.highlightAll();
}, [content]);
return (
<section className="codeGlobalContainer">
<textarea
className="codeInput"
spellCheck="false"
wrap="off"
cols="200"
value={content}
onChange={(e) => setContent(e.target.value)}
onKeyDown={(e) => handleKeyDown(e)}
/>
<pre className="codeOutput">
<code className={`language-javascript`}>{content}</code>
</pre>
</section>
);
}
I get that there's no CSS property like caret-blinking-speed, but is there anything I could do ? Is the handleKeyDown() function the source of my problem ?
N.B. : if I select text in my textarea, using keyboard, everything's going smoothly. Likewise, if I move my cursor with my keyboard arrows and type text instantly, there's no problem neither. It's really just the display of the caret.
Edit : If I remove the pre bloc, the problem is solved and the caret returns to its basic "reset blinking cycle on move" status.
After several tests, I've found out that it's the spellCheck="false" line in my textarea that creates my problem. So it's solved for now... But I don't know why.
I have a component called TextInput.vue, and inside I created a div.
<div ts-text-input :ts-text-input-filled="setFilledAttribute && !!value"
:ts-text-input-not-valid="!valueValid">
<input :value="value" #input="setValue" #keyup.enter="enterClicked" :placeholder="placeholder" :title="title">
what I wanted to do now is that to disable some spaces inside the input box so that the user is unable to type in with spaces/spacebar (like, e.g., username input box)
Here is what I have done; I try to use the function trim(), but it seems I can't still fix it.
in the computed function
computed: {
value: function() {
const {valueGetter, valueGetterOptions} = this,
getter = this.$store.getters[valueGetter];
value.trim();
return valueGetterOptions ? getter(valueGetterOptions) : getter;
},
Any hints would be helpful. thanks. (sorry for my bad English)
You can directly prevent that the user adds a white space to your input field. preventDefault() tells the user agent that the default action should not be taken as it normally would be.
<input #keydown.space="(event) => event.preventDefault()">
Or to make it even shorter (as Sovalina pointed out):
<input #keydown.space.prevent>
To prevent spaces on all input events - keypress, paste or drag and drop text:
const removeEventSpaces = e => {
e.preventDefault();
const left = e.target.value.substring(0, e.target.selectionStart);
const right = e.target.value.substring(e.target.selectionEnd, e.target.value.length);
const pasted = (e.dataTransfer || e.clipboardData).getData('text').replace(/ /g, '');
e.target.value = left + pasted + right;
}
<input #keydown.space.prevent #paste="removeEventSpaces" #drop="removeEventSpaces"/>
#keydown.native.space didn't work for me. #keydown.native.space.prevent did.
In this case, you can use Regular Expressions
value.replace(/\s/, '')
or to be sure the data is stored without any capital letters
value.replace(/\s/, '').toLowerCase()
u could use get and set
var inputData = ''
export default {
name: 'yourFromComponent',
computed: {
inputValue: {
get () {
return inputData
},
set (value) {
inputData = value.replace(/\s/g,'')
}
}
}
}
if you use vuex just change the inputData to your store referenz
I have the function below that is called on click of a button . Everything works well, but the document.execCommand ('copy') simply does not work.
If I create another button and call only the contents of if in a separate function, it works well.
I have already tried calling a second function inside the first one, but it also does not work. the copy is only working if it is alone in the function.
Does anyone know what's going on?
copyNshort = () => {
const bitly = new BitlyClient('...') // Generic Access Token bit.ly
let txt = document.getElementById('link-result')
bitly.shorten(txt.value)
.then((res) => {
this.setState({ shortedLink: res.url })
if (this.state.shortedLink !== undefined) {
document.getElementById('link-result-shorted').select() // get textarea value and select
document.execCommand('copy') // copy selected
console.log('The link has been shortened and copied to clipboard!')
ReactDOM.render(<i className="fas fa-clipboard-check"></i>, document.getElementById('copied'))
}
console.log('Shortened link 👉🏼', res.url) // Shorted url
})
}
The problem is that the copy-to-clipboard functionality will only work as a direct result of a user's click event listener... This event cannot be virtualised and the execCommand will not work anywhere else than the immediate callback assigned to the event listener...
Because react virtualises and abstracts 'events' then that's very possibly where the problem lies and as suggested you should be using React's react-copy-to-clipboard.
You can use lib react-copy-to-clipboard to copy text.
import {CopyToClipboard} from 'react-copy-to-clipboard';`
function(props) {
return (
<CopyToClipboard text={'Text will be copied'}>
<button>Copy button</button>
</CopyToClipboard>
);
}
if you click button Copy button, it will copy the text Text will be copied
The lib react-copy-to-clipboard based on copy-to-clipboard does work for me, but if you want to copy the source into your own file, Some places need attention.
The code below works fine.
import React, { Component } from 'react'
class App extends Component {
render() {
return (
<div className="App">
<h1
onClick={e => {
const range = document.createRange()
const selection = document.getSelection()
const mark = document.createElement('span')
mark.textContent = 'text to copy'
// reset user styles for span element
mark.style.all = 'unset'
// prevents scrolling to the end of the page
mark.style.position = 'fixed'
mark.style.top = 0
mark.style.clip = 'rect(0, 0, 0, 0)'
// used to preserve spaces and line breaks
mark.style.whiteSpace = 'pre'
// do not inherit user-select (it may be `none`)
mark.style.webkitUserSelect = 'text'
mark.style.MozUserSelect = 'text'
mark.style.msUserSelect = 'text'
mark.style.userSelect = 'text'
mark.addEventListener('copy', function(e) {
e.stopPropagation()
})
document.body.appendChild(mark)
// The following line is very important
if (selection.rangeCount > 0) {
selection.removeAllRanges()
}
range.selectNodeContents(mark)
selection.addRange(range)
document.execCommand('copy')
document.body.removeChild(mark)
}}
>
Click to Copy Text
</h1>
</div>
)
}
}
export default App
import React, { Component } from 'react'
class App extends Component {
render() {
return (
<div className="App">
<h1
onClick={e => {
const mark = document.createElement('textarea')
mark.setAttribute('readonly', 'readonly')
mark.value = 'copy me'
mark.style.position = 'fixed'
mark.style.top = 0
mark.style.clip = 'rect(0, 0, 0, 0)'
document.body.appendChild(mark)
mark.select()
document.execCommand('copy')
document.body.removeChild(mark)
}}
>
Click to Copy Text
</h1>
</div>
)
}
}
export default App
I have a React application which displays some spans:
<span>Hello</span> <span>my</span> <span>name</span> <span> is </span> ...
I would like the user to select the text with the mouse like so
..and then get the selected value, or highlight the text etc.
How would I do this in React? I am not sure what event handlers to use and how to get hold of the current selection! A minimal example or a hint would be appreciated!
Use onMouseUp and onDoubleClick events to detect to call method, that will determine selection using JavaScript Selection API.
Use window.getSelection() to get selection object.
To get selected text, use window.getSelection.toString().
To get coordinates of selected text area for rendering popup menu, use selection.getRangeAt(0).getBoundingClientRect().
As an example implementation, take a look at react-highlight library.
There is no React-specific solution for this. Just use window.getSelection API.
To output highlighted text run window.getSelection().toString()
Here's an example in React using a functional component:
const Post = () => {
const handleMouseUp() {
console.log(`Selected text: ${window.getSelection().toString()}`);
}
return (
<div onMouseUp={handleMouseUp}>Text</div>
);
}
As Lyubomir pointed out the window.getSelection() API does the trick!
I think that it's the right way...
document.onmouseup = () => {
console.log(window.getSelection().toString());
};
Step 1: - Initially Mouse will capture every time you enter something
<textarea type="text"
className="contentarea__form__input"
onMouseUpCapture={this.selectedText}>
</textarea>
Step 2: To make sure it only catches your selected text I used if-else condition
selectedText = () => {
window.getSelection().toString() ? console.log(window.getSelection().toString()) : null;
}
Due to this bug in Firefox, I was unable to get it working with
window.getSelection() as suggested by other answers.
So I had to do the following (in React):
constructor(props) {
this.textAreaRef = React.createRef();
}
getSelection() {
const textArea = (this.textAreaRef.current as HTMLTextAreaElement);
console.log(textArea.value.substring(
textArea.selectionStart,
textArea.selectionEnd
)
);
}
render() {
<textarea onMouseUp={() => this.getSelection()}
ref={this.textAreaRef}>
</textarea>
}
I just made a custom hook for this (in typescript if needed):
export const useSelectedText = () => {
const [text, setText] = useState('')
const select = () => {
const selected = window.getSelection() as Selection
setText(selected.toString())
}
return [select, text] as const
}
This can be done using react-text-annotate
library: https://www.npmjs.com/package/react-text-annotate
We can do using state simply to select and highlight them.
import styled from 'styled-components';
const ClipBoardLink = styled.span<{ selection: boolean }>`
background: ${(p) => (p.selection ? '#1597E5' : '')};
margin-left: 0.5rem;
color: ${(p) => (p.selection ? '#fff' : '')};
`;
const GroupOrderModel = () => {
const [isCopied, setIsCopied] = useState(false);
const handleIsClipboardCopy = async (text: string) => {
setIsCopied(true);
navigator.clipboard.writeText(text);
setTimeout(() => {
setIsCopied(false);
}, 5000);
};
return (
<ClipBoardLink selection={isCopied} onClick={async () => await handleIsClipboardCopy("Click me!")}>Click me!</ClipBoardLink>
);
}