Skip to content

Commit a9c2577

Browse files
authored
New autocomplete behavior (#107)
This commit changes autocomplete behavior once the item is selected. The autocomplete is hidden and it's going to be shown only if you type *trigger* again. This should be more natural. close #100 #101
1 parent f30dab7 commit a9c2577

File tree

4 files changed

+91
-5
lines changed

4 files changed

+91
-5
lines changed

cypress/integration/textarea.js

+20
Original file line numberDiff line numberDiff line change
@@ -289,5 +289,25 @@ describe("React Textarea Autocomplete", () => {
289289
"rta__autocomplete--top"
290290
);
291291
});
292+
293+
it("another example of advanced combo usage", () => {
294+
cy.get(".rta__textarea").type("This is test ({enter}");
295+
cy.get(".rta__textarea").type(".");
296+
cy.get(".rta__list")
297+
.get("li:nth-child(1)")
298+
.click();
299+
cy.get(".rta__textarea").should("have.value", "This is test (country.ID");
300+
});
301+
302+
it("once its selected dont show the autocomplete anymore", () => {
303+
cy.get(".rta__textarea").type("This is test (");
304+
cy.get(".rta__autocomplete").should("be.visible");
305+
cy.get(".rta__list")
306+
.get("li:nth-child(1)")
307+
.click();
308+
cy.get(".rta__textarea").should("have.value", "This is test (country");
309+
cy.get(".rta__textarea").type("somethingelse");
310+
cy.get(".rta__autocomplete").should("not.be.visible");
311+
});
292312
});
293313
});

example/App.jsx

+23
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,29 @@ class App extends React.Component {
327327
],
328328
component: Item,
329329
output: this._outputCaretEnd
330+
},
331+
"(": {
332+
dataProvider: token => [
333+
{ name: "country", char: "country" },
334+
{ name: "person", char: "person" }
335+
],
336+
component: Item,
337+
output: (item, trigger) => ({
338+
text: `${trigger}${item.name}`,
339+
caretPosition: "end"
340+
})
341+
},
342+
".": {
343+
dataProvider: token => [
344+
{ name: "ID", char: "ID" },
345+
{ name: "name", char: "name" },
346+
{ name: "someProperty", char: "someProperty" }
347+
],
348+
component: Item,
349+
output: (item, trigger) => ({
350+
text: `${trigger}${item.name}`,
351+
caretPosition: "end"
352+
})
330353
}
331354
}}
332355
/>

src/Textarea.jsx

+47-4
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ class ReactTextareaAutocomplete extends React.Component<
243243
}
244244

245245
if (oldValue !== value && this.lastValueBubbledEvent !== value) {
246+
this.lastTrigger = 0;
246247
this._changeHandler();
247248
}
248249
}
@@ -361,6 +362,11 @@ class ReactTextareaAutocomplete extends React.Component<
361362
dataLoading: false
362363
},
363364
() => {
365+
const insertedTrigger = this.tokenRegExpEnding.exec(newTokenString);
366+
const insertedTriggerModifier = insertedTrigger
367+
? insertedTrigger[0].length
368+
: 1;
369+
this.lastTrigger = newCaretPosition - insertedTriggerModifier;
364370
this.textareaRef.value = newValue;
365371
this._changeHandler();
366372

@@ -532,6 +538,22 @@ class ReactTextareaAutocomplete extends React.Component<
532538
.map(a => `\\${a}`)
533539
.join("|")})((?:(?!\\1)[^\\s])*$)`
534540
);
541+
542+
this.tokenRegExpEnding = new RegExp(
543+
`(${Object.keys(trigger)
544+
// the sort is important for multi-char combos as "/kick", "/"
545+
.sort((a, b) => {
546+
if (a < b) {
547+
return 1;
548+
}
549+
if (a > b) {
550+
return -1;
551+
}
552+
return 0;
553+
})
554+
.map(a => `\\${a}`)
555+
.join("|")})$`
556+
);
535557
};
536558

537559
/**
@@ -622,11 +644,28 @@ class ReactTextareaAutocomplete extends React.Component<
622644
value
623645
});
624646

625-
let tokenMatch = this.tokenRegExp.exec(value.slice(0, selectionEnd));
647+
const cleanLastTrigger = () => {
648+
this.lastTrigger = selectionEnd - 1;
649+
};
650+
651+
if (selectionEnd <= this.lastTrigger) {
652+
cleanLastTrigger();
653+
}
654+
655+
const affectedTextareaValue = value.slice(this.lastTrigger, selectionEnd);
656+
657+
let tokenMatch = this.tokenRegExp.exec(affectedTextareaValue);
626658
let lastToken = tokenMatch && tokenMatch[0];
627659

628660
let currentTrigger = (tokenMatch && tokenMatch[1]) || null;
629661

662+
// with this approach we want to know if the user just inserted a new trigger sequence
663+
const isNewTrigger = this.tokenRegExpEnding.exec(affectedTextareaValue);
664+
665+
if (isNewTrigger) {
666+
cleanLastTrigger();
667+
}
668+
630669
/*
631670
if we lost the trigger token or there is no following character we want to close
632671
the autocomplete
@@ -650,9 +689,8 @@ class ReactTextareaAutocomplete extends React.Component<
650689
*/
651690
if (
652691
currentTrigger &&
653-
value[tokenMatch.index - 1] &&
654-
(trigger[currentTrigger].afterWhitespace &&
655-
!value[tokenMatch.index - 1].match(/\s/))
692+
trigger[currentTrigger].afterWhitespace &&
693+
value[selectionEnd - 2] !== " "
656694
) {
657695
this._closeAutocomplete();
658696
return;
@@ -797,6 +835,11 @@ class ReactTextareaAutocomplete extends React.Component<
797835

798836
lastValueBubbledEvent: string;
799837

838+
tokenRegExpEnding: RegExp;
839+
840+
// Last trigger index, to know when user selected the item and we should stop showing the autocomplete
841+
lastTrigger: number = 0;
842+
800843
render() {
801844
const {
802845
loadingComponent: Loader,

webpack.prod.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ process.env.NODE_ENV = "production";
55
module.exports = Object.assign(webpackConfig, {
66
mode: process.env.NODE_ENV,
77
output: {
8-
path: __dirname + "/example-build",
8+
path: `${__dirname}/example-build`,
99
filename: "main.js"
1010
}
1111
});

0 commit comments

Comments
 (0)