2021-03-02 16:46:48 -05:00
|
|
|
import { html, Component, createRef } from "../lib/index.js";
|
2020-06-18 08:23:08 -04:00
|
|
|
|
|
|
|
export default class Composer extends Component {
|
|
|
|
state = {
|
|
|
|
text: "",
|
|
|
|
};
|
|
|
|
textInput = createRef();
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
2020-06-26 08:57:34 -04:00
|
|
|
this.handleInput = this.handleInput.bind(this);
|
2020-06-18 08:23:08 -04:00
|
|
|
this.handleSubmit = this.handleSubmit.bind(this);
|
2020-06-29 06:36:17 -04:00
|
|
|
this.handleInputKeyDown = this.handleInputKeyDown.bind(this);
|
2020-06-18 08:23:08 -04:00
|
|
|
this.handleWindowKeyDown = this.handleWindowKeyDown.bind(this);
|
|
|
|
}
|
|
|
|
|
2020-06-26 08:57:34 -04:00
|
|
|
handleInput(event) {
|
2020-06-18 08:23:08 -04:00
|
|
|
this.setState({ [event.target.name]: event.target.value });
|
2021-06-07 09:18:00 -04:00
|
|
|
|
|
|
|
if (this.props.readOnly && event.target.name === "text" && !event.target.value) {
|
|
|
|
event.target.blur();
|
|
|
|
}
|
2020-06-18 08:23:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
handleSubmit(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
this.props.onSubmit(this.state.text);
|
|
|
|
this.setState({ text: "" });
|
|
|
|
}
|
|
|
|
|
2020-06-29 06:36:17 -04:00
|
|
|
handleInputKeyDown(event) {
|
2021-06-30 15:50:55 -04:00
|
|
|
let input = event.target;
|
|
|
|
|
2020-06-29 06:36:17 -04:00
|
|
|
if (!this.props.autocomplete || event.key !== "Tab") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-30 15:50:55 -04:00
|
|
|
if (input.selectionStart !== input.selectionEnd) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
let carretIndex = input.selectionStart;
|
2021-06-10 12:11:11 -04:00
|
|
|
let text = this.state.text;
|
2021-06-30 15:50:55 -04:00
|
|
|
let wordStart;
|
|
|
|
for (wordStart = carretIndex - 1; wordStart >= 0; wordStart--) {
|
|
|
|
if (text[wordStart] === " ") {
|
2020-06-29 06:36:17 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-06-30 15:50:55 -04:00
|
|
|
wordStart++;
|
|
|
|
|
|
|
|
let wordEnd;
|
|
|
|
for (wordEnd = carretIndex; wordEnd < text.length; wordEnd++) {
|
|
|
|
if (text[wordEnd] === " ") {
|
|
|
|
break;
|
|
|
|
}
|
2020-06-29 06:36:17 -04:00
|
|
|
}
|
|
|
|
|
2021-06-30 15:50:55 -04:00
|
|
|
let word = text.slice(wordStart, wordEnd);
|
|
|
|
if (!word) {
|
|
|
|
return;
|
|
|
|
}
|
2020-06-29 06:36:17 -04:00
|
|
|
|
2021-06-30 15:50:55 -04:00
|
|
|
let repl = this.props.autocomplete(word);
|
2020-06-29 06:36:17 -04:00
|
|
|
if (!repl) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-30 15:50:55 -04:00
|
|
|
text = text.slice(0, wordStart) + repl + text.slice(wordEnd);
|
|
|
|
|
|
|
|
input.value = text;
|
|
|
|
input.selectionStart = wordStart + repl.length;
|
|
|
|
input.selectionEnd = input.selectionStart;
|
|
|
|
|
2020-06-29 06:36:17 -04:00
|
|
|
this.setState({ text });
|
|
|
|
}
|
|
|
|
|
2020-06-18 08:23:08 -04:00
|
|
|
handleWindowKeyDown(event) {
|
2021-06-22 08:44:20 -04:00
|
|
|
// If an <input> or <button> is focused, ignore.
|
|
|
|
if (document.activeElement !== document.body && document.activeElement.tagName !== "SECTION") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-22 11:25:24 -04:00
|
|
|
// If a modifier is pressed, reserve for key bindings.
|
|
|
|
if (event.altKey || event.ctrlKey || event.metaKey) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-22 08:44:20 -04:00
|
|
|
// Ignore events that don't produce a Unicode string. If the key event
|
|
|
|
// result in a character being typed by the user, KeyboardEvent.key
|
|
|
|
// will contain the typed string. The key string may contain one
|
|
|
|
// Unicode non-control character and multiple Unicode combining
|
|
|
|
// characters. String.prototype.length cannot be used since it would
|
|
|
|
// return the number of Unicode code-points. Instead, the spread
|
|
|
|
// operator is used to count the number of non-combining Unicode
|
|
|
|
// characters.
|
|
|
|
if ([...event.key].length !== 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.state.text) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.props.readOnly && event.key !== "/") {
|
|
|
|
return;
|
2020-06-18 08:23:08 -04:00
|
|
|
}
|
2021-06-22 08:44:20 -04:00
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
this.setState({ text: event.key }, () => {
|
|
|
|
this.focus();
|
|
|
|
});
|
2020-06-18 08:23:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
window.addEventListener("keydown", this.handleWindowKeyDown);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
window.removeEventListener("keydown", this.handleWindowKeyDown);
|
|
|
|
}
|
|
|
|
|
|
|
|
focus() {
|
2020-07-13 03:45:30 -04:00
|
|
|
if (!this.textInput.current) {
|
|
|
|
return;
|
|
|
|
}
|
2020-06-18 08:23:08 -04:00
|
|
|
document.activeElement.blur(); // in case we're read-only
|
|
|
|
this.textInput.current.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2021-06-22 07:38:05 -04:00
|
|
|
let className = "";
|
|
|
|
if (this.props.readOnly && !this.state.text) {
|
|
|
|
className = "read-only";
|
|
|
|
}
|
|
|
|
|
2020-06-18 08:23:08 -04:00
|
|
|
return html`
|
2021-06-22 07:38:05 -04:00
|
|
|
<form
|
|
|
|
id="composer"
|
|
|
|
class=${className}
|
|
|
|
onInput=${this.handleInput}
|
|
|
|
onSubmit=${this.handleSubmit}
|
|
|
|
>
|
2021-05-27 18:16:05 -04:00
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
name="text"
|
|
|
|
ref=${this.textInput}
|
|
|
|
value=${this.state.text}
|
|
|
|
autocomplete="off"
|
|
|
|
placeholder="Type a message"
|
2021-05-31 12:16:49 -04:00
|
|
|
enterkeyhint="send"
|
2021-05-27 18:16:05 -04:00
|
|
|
onKeyDown=${this.handleInputKeyDown}
|
|
|
|
/>
|
2020-06-18 08:23:08 -04:00
|
|
|
</form>
|
|
|
|
`;
|
|
|
|
}
|
|
|
|
}
|