site: decouple state from event handlers
Try to avoid directly setting state from event handlers and instead provide utility functions that mutate state in a consistent way.
This commit is contained in:
parent
b1cd0dad8d
commit
8168bb9715
2 changed files with 42 additions and 29 deletions
|
@ -6,7 +6,7 @@ function mySplice(s, idx, del, n) {
|
||||||
return s.substring(0, idx) + n + s.substring(idx + del);
|
return s.substring(0, idx) + n + s.substring(idx + del);
|
||||||
}
|
}
|
||||||
|
|
||||||
function suggestionPosition(el) {
|
function suggestionIndex(el) {
|
||||||
// el.parentNode is ul#tags-suggestion, which include a template. That's why
|
// el.parentNode is ul#tags-suggestion, which include a template. That's why
|
||||||
// a -1 is needed here.
|
// a -1 is needed here.
|
||||||
const idx = [...el.parentNode.children].indexOf(el);
|
const idx = [...el.parentNode.children].indexOf(el);
|
||||||
|
@ -52,50 +52,63 @@ document.addEventListener('alpine:init', () => {
|
||||||
this.suggestions = await response.json();
|
this.suggestions = await response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
completeSuggestion(evt) {
|
completeSuggestion() {
|
||||||
if (this.currentSuggestion === null)
|
if (this.currentSuggestion === null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const value = this.$refs.tags.value;
|
const value = this.$refs.tags.value;
|
||||||
const pos = this.inputCursor - this.query.length;
|
const pos = Math.max(this.inputCursor - this.query.length, 0);
|
||||||
const tag = this.suggestions[this.currentSuggestion].display;
|
const tag = this.suggestions[this.currentSuggestion].display;
|
||||||
|
const newValue = mySplice(value, pos, this.query.length, tag + ' ');
|
||||||
|
|
||||||
this.$refs.tags.value = mySplice(value, pos, this.query.length,
|
|
||||||
tag + ' ');
|
|
||||||
this.reset();
|
this.reset();
|
||||||
|
|
||||||
|
this.$refs.tags.value = newValue;
|
||||||
this.inputCursor =
|
this.inputCursor =
|
||||||
this.$refs.tags.selectionStart =
|
this.$refs.tags.selectionStart =
|
||||||
this.$refs.tags.selectionEnd =
|
this.$refs.tags.selectionEnd =
|
||||||
pos + tag.length + 1;
|
pos + tag.length + 1;
|
||||||
|
|
||||||
this.$refs.tags.focus();
|
this.$refs.tags.focus();
|
||||||
|
|
||||||
// Don't switch focus if we're completing after a Tab press.
|
|
||||||
if (evt.type === "keydown" && evt.keyCode === 9)
|
|
||||||
evt.preventDefault();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
selectCurrentSuggestion(evt) {
|
completeClickedSuggestion(evt) {
|
||||||
this.currentSuggestion = suggestionPosition(evt.currentTarget);
|
this.currentSuggestion = suggestionIndex(evt.currentTarget);
|
||||||
this.completeSuggestion(evt);
|
this.completeSuggestion();
|
||||||
},
|
},
|
||||||
|
|
||||||
setCurrentSuggestion(evt, amount = 0) {
|
setCurrentSuggestion(idx) {
|
||||||
|
const len = this.suggestions.length;
|
||||||
|
if (len === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
currentSuggestionElement(this.currentSuggestion)
|
currentSuggestionElement(this.currentSuggestion)
|
||||||
?.classList.remove("suggestion-hover");
|
?.classList.remove("suggestion-hover");
|
||||||
|
|
||||||
if (evt.type === 'mouseenter')
|
idx = idx % len;
|
||||||
this.currentSuggestion = suggestionPosition(evt.currentTarget);
|
if (idx < 0)
|
||||||
else {
|
idx += len;
|
||||||
const len = this.suggestions.length;
|
this.currentSuggestion = idx;
|
||||||
let cur = this.currentSuggestion;
|
|
||||||
|
|
||||||
if (cur === null)
|
|
||||||
cur = amount > 0 ? -1 : amount < 0 ? len : 0;
|
|
||||||
this.currentSuggestion = (cur + amount + len) % len;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentSuggestionElement(this.currentSuggestion)
|
currentSuggestionElement(this.currentSuggestion)
|
||||||
?.classList.add("suggestion-hover");
|
?.classList.add("suggestion-hover");
|
||||||
}
|
},
|
||||||
|
|
||||||
|
setPointerSuggestion(evt) {
|
||||||
|
this.setCurrentSuggestion(suggestionIndex(evt.currentTarget));
|
||||||
|
},
|
||||||
|
|
||||||
|
moveCurrentSuggestion(amount, defaultValue) {
|
||||||
|
this.setCurrentSuggestion(this.currentSuggestion === null ?
|
||||||
|
defaultValue : this.currentSuggestion + amount);
|
||||||
|
},
|
||||||
|
|
||||||
|
nextSuggestion(evt) {
|
||||||
|
this.moveCurrentSuggestion(1, 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
previousSuggestion(evt) {
|
||||||
|
this.moveCurrentSuggestion(-1, -1);
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,16 +11,16 @@
|
||||||
x-ref="tags"
|
x-ref="tags"
|
||||||
@focus="fetchSuggestions"
|
@focus="fetchSuggestions"
|
||||||
@input.debounce="fetchSuggestions"
|
@input.debounce="fetchSuggestions"
|
||||||
@keydown.tab="completeSuggestion"
|
@keydown.tab.prevent="completeSuggestion"
|
||||||
@keydown.up.prevent="setCurrentSuggestion($event, -1)"
|
@keydown.up.prevent="previousSuggestion"
|
||||||
@keydown.down.prevent="setCurrentSuggestion($event, +1)"
|
@keydown.down.prevent="nextSuggestion"
|
||||||
@keydown.escape="reset">
|
@keydown.escape="reset">
|
||||||
<ul id="tags-suggestions"
|
<ul id="tags-suggestions"
|
||||||
x-cloak x-show="suggestions.length > 0">
|
x-cloak x-show="suggestions.length > 0">
|
||||||
<template x-for="suggestion in suggestions">
|
<template x-for="suggestion in suggestions">
|
||||||
<li
|
<li
|
||||||
@click="selectCurrentSuggestion"
|
@click="completeClickedSuggestion"
|
||||||
@mouseenter="setCurrentSuggestion">
|
@mouseenter="setPointerSuggestion">
|
||||||
<span
|
<span
|
||||||
x-text="suggestion.display"
|
x-text="suggestion.display"
|
||||||
:class="suggestion.kind_class">
|
:class="suggestion.kind_class">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue