From e6f9e8c4f4ae9fd77264a7a065182600ef62210f Mon Sep 17 00:00:00 2001 From: armandgray Date: Wed, 21 Feb 2018 12:00:47 -0800 Subject: [PATCH 01/49] [#154882915] adjusting height of suggestions panel on the fly to account for data length --- src/MentionsTextInput.js | 493 +++++++++++++++++++++++++++++++++++---- 1 file changed, 444 insertions(+), 49 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 41d9d6b..45379a7 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -9,98 +9,484 @@ import { } from 'react-native'; import PropTypes from 'prop-types'; +const SET_STATE_DELAY = 50; + export default class MentionsTextInput extends Component { - constructor() { - super(); + constructor(props) { + super(props); + this.state = { - textInputHeight: "", - isTrackingStarted: false, + textInputHeight: '', suggestionRowHeight: new Animated.Value(0), + text: this.props.value ? this.props.value : '', + }; - } + this.lastTextLength = 0; + this.lastTriggerIndex = 0; + this.triggerMatrix = []; this.isTrackingStarted = false; - this.previousChar = " "; } componentWillMount() { this.setState({ textInputHeight: this.props.textInputMinHeight - }) + }); + } + + reloadTriggerMatrix(text) { + this.triggerMatrix = []; + let start = 0; + let triggered = false; + for (let i = 0; i < text.length; i++) { + if (!triggered && text[i] === '@' && (i == 0 || text[i - 1] === ' ')) { + start = i; + triggered = true; + } else if (triggered && text[i] === ' ') { + this.triggerMatrix.push([start, i - 1]); + triggered = false; + } else if (triggered && i === text.length - 1) { + this.triggerMatrix.push([start, i]); + } + } } componentWillReceiveProps(nextProps) { if (!nextProps.value) { this.resetTextbox(); - } else if (this.isTrackingStarted && !nextProps.horizontal && nextProps.suggestionsData.length !== 0) { - const numOfRows = nextProps.MaxVisibleRowCount >= nextProps.suggestionsData.length ? nextProps.suggestionsData.length : nextProps.MaxVisibleRowCount; - const height = numOfRows * nextProps.suggestionRowHeight; - this.openSuggestionsPanel(height); } + + setTimeout(() => { + if (this.isTrackingStarted && nextProps.didPressSuggestion && nextProps.value != this.state.text && !this.didDeleteTriggerKeyword) { + this.reloadTriggerMatrix(nextProps.value); + if (this.props.triggerCallback && this.triggerMatrix) { + const keyword = this.triggerMatrix.length ? this.triggerMatrix[this.triggerMatrix.length - 1] : null; + const index = this.triggerMatrix.length ? this.triggerMatrix.length - 1 : null; + this.props.triggerCallback(keyword, this.triggerMatrix, index); + } + + this.stopTracking(); + this.setState({ text: nextProps.value }); + } + }, SET_STATE_DELAY); } - startTracking() { - this.isTrackingStarted = true; - this.openSuggestionsPanel(); + resetTextbox() { + this.triggerMatrix = []; this.setState({ - isTrackingStarted: true - }) + textInputHeight: this.props.textInputMinHeight, + text: '', + }); } - stopTracking() { - this.isTrackingStarted = false; - this.closeSuggestionsPanel(); - this.setState({ - isTrackingStarted: false - }) + handleReset() { + this.didTextChange = false; + this.isTriggerDeleted = false; + this.didPropsChangeText = false; + this.lastTextLength = this.state.text.length; } - openSuggestionsPanel(height) { - Animated.timing(this.state.suggestionRowHeight, { - toValue: height ? height : this.props.suggestionRowHeight, + openSuggestionsPanel() { + if (this.props.onOpenSuggestionsPanel) { + this.props.onOpenSuggestionsPanel(); + } + + let numOfRows = 0; + if (this.props.suggestionsData) { + const isDataLengthBelowMax = this.props.MaxVisibleRowCount >= this.props.suggestionsData.length; + numOfRows = isDataLengthBelowMax ? this.props.suggestionsData.length : this.props.MaxVisibleRowCount; + } + + Animated.spring(this.state.suggestionRowHeight, { duration: 100, + toValue: numOfRows * this.props.suggestionRowHeight, + friction: 4, }).start(); } closeSuggestionsPanel() { + if (this.props.onCloseSuggestionsPanel) { + this.props.onCloseSuggestionsPanel(); + } + Animated.timing(this.state.suggestionRowHeight, { toValue: 0, duration: 100, }).start(); } - updateSuggestions(lastKeyword) { - this.props.triggerCallback(lastKeyword); + handleDisplaySuggestions(position) { + if (!this.triggerMatrix + || !this.triggerMatrix.length + || this.shouldDeleteTriggerOnBackspace) { + return; + } + + if (!this.isTrackingStarted) { + this.closeSuggestionsPanel(); + return; + } + + const keyword = this.getTriggerKeyword(position); + const delay = this.props.triggerDelay ? this.props.triggerDelay : 0; + if (keyword && keyword.length > delay) { + this.openSuggestionsPanel(); + if (this.props.triggerCallback) { + this.props.triggerCallback(keyword, this.triggerMatrix, this.getSubsequentTriggerIndex(position)); + } + } else { + this.closeSuggestionsPanel(); + } } - identifyKeyword(val) { - if (this.isTrackingStarted) { - const boundary = this.props.triggerLocation === 'new-word-only' ? 'B' : ''; - const pattern = new RegExp(`\\${boundary}${this.props.trigger}[a-z0-9_-]+|\\${boundary}${this.props.trigger}`, `gi`); - const keywordArray = val.match(pattern); - if (keywordArray && !!keywordArray.length) { - const lastKeyword = keywordArray[keywordArray.length - 1]; - this.updateSuggestions(lastKeyword); + handleDeleteTriggerOnBackspace(position = 0, index = -2) { // eslint-disable-line no-magic-numbers + if (!this.triggerMatrix || !this.triggerMatrix.length || this.didPropsChangeText) { + return; + } + + if (index === -2) { // eslint-disable-line no-magic-numbers + index = this.getSubsequentTriggerIndex(position); + } + + const isAtEnd = position === this.state.text.length - 1; + const isAtEndOfTrigger = this.triggerMatrix[index][1] === position; + const isFollowedBySpace = this.state.text[position + 1] === ' '; + + this.shouldDeleteTriggerOnBackspace = isAtEndOfTrigger && (isAtEnd || isFollowedBySpace); + } + + handleClick(position) { + if (!this.triggerMatrix || !this.triggerMatrix.length) { + return; + } + + const index = this.getSubsequentTriggerIndex(position); + this.handleDeleteTriggerOnBackspace(position, index); + + if (this.isPositionWithinTrigger(position, index)) { + this.startTracking(position, index); + return; + } + + + if (position === -1 // eslint-disable-line no-magic-numbers + || this.state.text && this.state.text[position] === ' ' + || !this.isPositionWithinTrigger(position)) { + this.stopTracking(); + } + } + + handleTriggerSplitBySpace(position) { + if (!this.triggerMatrix + || !this.triggerMatrix.length + || !this.isTrackingStarted + || position < 1 + || position >= this.state.text.length) { + return; + } + + const index = this.getSubsequentTriggerIndex(position); + this.triggerMatrix[index][1] = position - 1; + } + + isTriggerSplitBySpace(position) { + if (!this.triggerMatrix || !this.triggerMatrix.length) { + return false; + } + + const index = this.getSubsequentTriggerIndex(position); + return this.isPositionWithinTrigger(position, index) + && this.isTrackingStarted + && this.state.text[position] === ' '; + } + + getDistanceToNextSpace(index = -1) { // eslint-disable-line no-magic-numbers + if (index === -1 || !this.state.text || !this.state.text.length || index > this.state.text.length) { // eslint-disable-line no-magic-numbers + return 0; + } + + const spaceIndex = this.state.text.indexOf(' ', index); + return spaceIndex === -1 ? this.state.text.length - 1 - index : spaceIndex - index; // eslint-disable-line no-magic-numbers + } + + getTriggerKeyword(position, index = -2) { // eslint-disable-line no-magic-numbers + if (!this.triggerMatrix || !this.triggerMatrix.length || !this.isTrackingStarted) { + return; + } + + if (index === -2) { // eslint-disable-line no-magic-numbers + index = this.getSubsequentTriggerIndex(position); + } + + if (index === -1 || index >= this.triggerMatrix.length) { // eslint-disable-line no-magic-numbers + return; + } + + const start = this.triggerMatrix[index][0]; + const end = this.triggerMatrix[index][1]; + const pattern = new RegExp(`${this.props.trigger}[a-z0-9_-]*`, `gi`); + const triggerText = this.state.text.slice(start, end + this.getDistanceToNextSpace(end) + 1); + const keywordArray = triggerText.match(pattern); + + return keywordArray && keywordArray.length ? keywordArray[0] : ''; + } + + updateTriggerMatrixIndex(position, index = -2) { // eslint-disable-line no-magic-numbers + if (!this.triggerMatrix || !this.triggerMatrix.length || !this.isTrackingStarted) { + return; + } + + if (index === -2) { // eslint-disable-line no-magic-numbers + index = this.getSubsequentTriggerIndex(position); + } + + const keyword = this.getTriggerKeyword(position, index); + if (!keyword || !keyword.length) { + return; + } + + this.triggerMatrix[index][1] = this.triggerMatrix[index][0] + keyword.length - 1; + } + + stopTracking() { + this.closeSuggestionsPanel(); + this.isTrackingStarted = false; + } + + getSubsequentTriggerIndex(position, start = 0, end = Number.MAX_SAFE_INTEGER, lastBiggerIndex = -1) { + if (!this.triggerMatrix || !this.triggerMatrix.length || start > end) { + return lastBiggerIndex; + } + + if (lastBiggerIndex == -1) { // eslint-disable-line no-magic-numbers + lastBiggerIndex = this.triggerMatrix.length - 1; + } + + if (end === Number.MAX_SAFE_INTEGER) { + end = this.triggerMatrix.length - 1; + } + + + if (start == end) { + return this.triggerMatrix[start][0] <= position && position <= this.triggerMatrix[start][1] ? start : lastBiggerIndex; + } + + const middle = Math.trunc((start + end) / 2); + if (this.triggerMatrix[middle][0] <= position && position <= this.triggerMatrix[middle][1]) { + return middle; + + } else if (this.triggerMatrix[middle][1] < position) { + return this.getSubsequentTriggerIndex(position, middle + 1, end, lastBiggerIndex); + + } else { + return this.getSubsequentTriggerIndex(position, start, middle - 1, middle); + } + } + + isPositionWithinTrigger(position = 0, index = -2) { // eslint-disable-line no-magic-numbers + if (index === -2) { // eslint-disable-line no-magic-numbers + index = this.getSubsequentTriggerIndex(position); + } + + return this.triggerMatrix + && this.triggerMatrix.length + && this.triggerMatrix[index][0] <= position + && position <= this.triggerMatrix[index][1]; + } + + isPositionAfterBiggestTrigger(position = 0, index = 0) { + return !this.triggerMatrix + || !this.triggerMatrix.length + || this.triggerMatrix[index][1] < position + && index === this.triggerMatrix.length - 1; + } + + isPositionBeforeNextTrigger(position = 0, index = 0) { + return !this.triggerMatrix + || !this.triggerMatrix.length + || position < this.triggerMatrix[index][0]; + } + + isTriggerMatrixEmpty(index = 0) { + return index === -1; // eslint-disable-line no-magic-numbers + } + + startTracking(position, index = -2) { // eslint-disable-line no-magic-numbers + this.isTrackingStarted = true; + + if (index === -2) { // eslint-disable-line no-magic-numbers + index = this.getSubsequentTriggerIndex(position); + } + + if (this.isTriggerMatrixEmpty(index)) { + this.triggerMatrix = [[position, position]]; + this.lastTriggerIndex = 0; + + } else if (this.isPositionBeforeNextTrigger(position, index)) { + this.triggerMatrix.splice(index, 0, [position, position]); + this.lastTriggerIndex = index; + + } else if (this.isPositionAfterBiggestTrigger(position, index)) { + this.triggerMatrix.push([position, position]); + this.lastTriggerIndex = this.triggerMatrix.length - 1; + + } else if (this.isPositionWithinTrigger(position, index)) { + this.lastTriggerIndex = index; + } + } + + handleTriggerMatrixShiftRight(position, index) { + if (this.isPositionAfterBiggestTrigger(position, index)) { + return; + } + + for (let i = index; i < this.triggerMatrix.length; i++) { + if (this.isPositionWithinTrigger(position - 1, i)) { + continue; + } + + this.triggerMatrix[i][0] += this.getTextDifference(); + this.triggerMatrix[i][1] += this.getTextDifference(); + } + } + + deleteTriggerKeyword(index, addSpace) { + const start = this.triggerMatrix[index][0]; + const end = this.triggerMatrix[index][1]; + + if (start >= end) { + return; + } + + const preTriggerText = this.state.text.slice(0, start + 1); + const postTriggerText = this.state.text.slice(end, this.state.text.length); + const space = postTriggerText.length && addSpace ? ' ' : ''; + + this.handleTriggerDeletion(index); + + setTimeout(() => { + this.didTextChange = true; + this.didDeleteTriggerKeyword = true; + this.shouldDeleteTriggerOnBackspace = false; + this.handleTriggerMatrixShiftLeft(start - 1, this.getSubsequentTriggerIndex(start), 1); + this.setState({ text: preTriggerText + space + postTriggerText }, () => { + this.startTracking(start); + }); + }, SET_STATE_DELAY); + } + + handleTriggerDeletion(index) { + this.isTriggerDeleted = true; + this.triggerMatrix.splice(index, 1); + } + + handleTriggerMatrixShiftLeft(position, index, difference = -this.getTextDifference()) { + if (!this.triggerMatrix + || this.triggerMatrix.length <= index + || this.isPositionAfterBiggestTrigger(position, index)) { + return; + } + + if (this.shouldDeleteTriggerOnBackspace) { + this.deleteTriggerKeyword(index); + return; + } + + if (position === this.triggerMatrix[index][0] - 1) { + this.handleTriggerDeletion(index); + if (this.triggerMatrix.length <= index) { + return; } } + + for (let i = index; i < this.triggerMatrix.length; i++) { + if (this.isPositionWithinTrigger(position, i)) { + continue; + } + + this.triggerMatrix[i][0] -= difference; + this.triggerMatrix[i][1] -= difference; + } + } + + getTextDifference() { + return this.state.text.length - this.lastTextLength; + } + + handleTriggerMatrixChanges(position, index = -2) { // eslint-disable-line no-magic-numbers + if (!this.triggerMatrix || !this.triggerMatrix.length || this.getTextDifference() == 0) { + return; + } + + if (index === -2) { // eslint-disable-line no-magic-numbers + index = this.getSubsequentTriggerIndex(position); + } + + if (index === -1 || index >= this.triggerMatrix.length) { // eslint-disable-line no-magic-numbers + return; + } + + if (this.getTextDifference() < 0) { + this.handleTriggerMatrixShiftLeft(position, index); + } else { + this.handleTriggerMatrixShiftRight(position, index); + this.shouldDeleteTriggerOnBackspace = false; + } } - onChangeText(val) { - this.props.onChangeText(val); // pass changed text back - const lastChar = val.substr(val.length - 1); - const wordBoundry = (this.props.triggerLocation === 'new-word-only') ? this.previousChar.trim().length === 0 : true; - if (lastChar === this.props.trigger && wordBoundry) { - this.startTracking(); - } else if (lastChar === ' ' && this.state.isTrackingStarted || val === "") { + handleTyping(position) { + const lastChar = this.state.text[position]; + const wordBoundary = (this.props.triggerLocation === 'new-word-only') ? position === 0 || this.state.text[position - 1] === ' ' : true; + + this.handleTriggerMatrixChanges(position); + this.handleDeleteTriggerOnBackspace(position); + + if (this.isTriggerDeleted) { + this.stopTracking(); + + } else if (!this.isTrackingStarted && lastChar === this.props.trigger && wordBoundary) { + this.startTracking(position); + + } else if (this.isTriggerSplitBySpace(position)) { + this.handleTriggerSplitBySpace(position); this.stopTracking(); + + } else if (this.isTrackingStarted && (lastChar === ' ' || this.state.text === '')) { + this.stopTracking(); + + } else if (this.isTrackingStarted) { + this.updateTriggerMatrixIndex(position - 1); } - this.previousChar = lastChar; - this.identifyKeyword(val); } - resetTextbox() { - this.previousChar = " "; - this.stopTracking(); - this.setState({ textInputHeight: this.props.textInputMinHeight }); + onSelectionChange(selection) { + if (this.props.onSelectionChange) { + this.props.onSelectionChange(); + } + + this.didDeleteTriggerKeyword = false; + + const position = selection.end - 1; + if (this.didTextChange && selection.start === selection.end) { + this.handleTyping(position); + + } else if (selection.start === selection.end) { + this.handleClick(position); + + } else { + // cursor selecting chars from selection.start to selection.end + } + + this.handleDisplaySuggestions(position); + this.handleReset(); + } + + onChangeText(text) { + if (this.props.onChangeText) { + this.props.onChangeText(text); + } + + this.didTextChange = true; + this.setState({ text }); } render() { @@ -126,13 +512,22 @@ export default class MentionsTextInput extends Component { }} ref={component => this._textInput = component} onChangeText={this.onChangeText.bind(this)} + onSelectionChange={(event) => { this.onSelectionChange(event.nativeEvent.selection); }} + returnKeyType={this.props.returnKeyType ? this.props.returnKeyType : 'send'} + maxLength={this.props.maxLength ? this.props.maxLength : Number.MAX_SAFE_INTEGER} + enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically ? this.props.enablesReturnKeyAutomatically : false} + underlineColorAndroid={this.props.underlineColorAndroid ? this.props.underlineColorAndroid : 'black'} + editable={this.props.editable ? this.props.editable : true} + onFocus={ () => {if (this.props.onFocus) {this.props.onFocus();}} } + onBlur={ () => {if (this.props.onBlur) {this.props.onBlur();}} } multiline={true} value={this.props.value} style={[{ ...this.props.textInputStyle }, { height: Math.min(this.props.textInputMaxHeight, this.state.textInputHeight) }]} placeholder={this.props.placeholder ? this.props.placeholder : 'Write a comment...'} + onKeyPress={(e) => { if (this.props.onKeyPress) {this.props.onKeyPress(e);} }} /> - ) + ); } } From 5d58ce91deb3f83aa6431a5047df62553814f854 Mon Sep 17 00:00:00 2001 From: armandgray Date: Wed, 21 Feb 2018 12:09:08 -0800 Subject: [PATCH 02/49] fixing delete rendering --- src/MentionsTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 45379a7..879c93b 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -521,7 +521,7 @@ export default class MentionsTextInput extends Component { onFocus={ () => {if (this.props.onFocus) {this.props.onFocus();}} } onBlur={ () => {if (this.props.onBlur) {this.props.onBlur();}} } multiline={true} - value={this.props.value} + value={this.state.text} style={[{ ...this.props.textInputStyle }, { height: Math.min(this.props.textInputMaxHeight, this.state.textInputHeight) }]} placeholder={this.props.placeholder ? this.props.placeholder : 'Write a comment...'} onKeyPress={(e) => { if (this.props.onKeyPress) {this.props.onKeyPress(e);} }} From f888b8a71a963fba87ea096efb6d8ab0af00d367 Mon Sep 17 00:00:00 2001 From: armandgray Date: Wed, 21 Feb 2018 12:22:37 -0800 Subject: [PATCH 03/49] fixing issue with android dialog suggestion click triggerMatrix indices: --- src/MentionsTextInput.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 879c93b..0011209 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -34,6 +34,10 @@ export default class MentionsTextInput extends Component { } reloadTriggerMatrix(text) { + if (!text) { + text = this.state.text; + } + this.triggerMatrix = []; let start = 0; let triggered = false; @@ -199,6 +203,13 @@ export default class MentionsTextInput extends Component { && this.state.text[position] === ' '; } + isSelectionReplaced() { + return this.triggerMatrix + && this.triggerMatrix.length + && this.state.text + && this.state.text[this.triggerMatrix[this.triggerMatrix.length - 1][0]] != '@'; + } + getDistanceToNextSpace(index = -1) { // eslint-disable-line no-magic-numbers if (index === -1 || !this.state.text || !this.state.text.length || index > this.state.text.length) { // eslint-disable-line no-magic-numbers return 0; @@ -443,6 +454,9 @@ export default class MentionsTextInput extends Component { if (this.isTriggerDeleted) { this.stopTracking(); + } else if (this.isSelectionReplaced()) { + this.reloadTriggerMatrix(); + } else if (!this.isTrackingStarted && lastChar === this.props.trigger && wordBoundary) { this.startTracking(position); @@ -500,7 +514,7 @@ export default class MentionsTextInput extends Component { enableEmptySections={true} data={this.props.suggestionsData} keyExtractor={this.props.keyExtractor} - renderItem={(rowData) => { return this.props.renderSuggestionsRow(rowData, this.stopTracking.bind(this)) }} + renderItem={(rowData) => { return this.props.renderSuggestionsRow(rowData, this.stopTracking.bind(this)); }} /> Date: Wed, 21 Feb 2018 12:25:44 -0800 Subject: [PATCH 04/49] fixing rowData rendering by passing rowData.item back to SDK --- src/MentionsTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 0011209..5cc16fa 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -514,7 +514,7 @@ export default class MentionsTextInput extends Component { enableEmptySections={true} data={this.props.suggestionsData} keyExtractor={this.props.keyExtractor} - renderItem={(rowData) => { return this.props.renderSuggestionsRow(rowData, this.stopTracking.bind(this)); }} + renderItem={(rowData) => { return this.props.renderSuggestionsRow(rowData.item, this.stopTracking.bind(this)); }} /> Date: Wed, 21 Feb 2018 12:56:39 -0800 Subject: [PATCH 05/49] removing animations from mentions list popping up --- src/MentionsTextInput.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 5cc16fa..fc400e0 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -100,10 +100,9 @@ export default class MentionsTextInput extends Component { numOfRows = isDataLengthBelowMax ? this.props.suggestionsData.length : this.props.MaxVisibleRowCount; } - Animated.spring(this.state.suggestionRowHeight, { - duration: 100, + Animated.timing(this.state.suggestionRowHeight, { + duration: 0, toValue: numOfRows * this.props.suggestionRowHeight, - friction: 4, }).start(); } @@ -114,7 +113,7 @@ export default class MentionsTextInput extends Component { Animated.timing(this.state.suggestionRowHeight, { toValue: 0, - duration: 100, + duration: 0, }).start(); } From 6bd83096e6e8eda36234eeafafdd8b9ce5cfc5e2 Mon Sep 17 00:00:00 2001 From: armandgray Date: Wed, 21 Feb 2018 13:10:04 -0800 Subject: [PATCH 06/49] adding prop ItemSeparatorComponent to FlatList --- src/MentionsTextInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index fc400e0..5a522a3 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -510,6 +510,7 @@ export default class MentionsTextInput extends Component { keyboardShouldPersistTaps={"always"} horizontal={this.props.horizontal} ListEmptyComponent={this.props.loadingComponent} + ItemSeparatorComponent={() => { return this.props.ItemSeparatorComponent ? this.props.ItemSeparatorComponent() : }} enableEmptySections={true} data={this.props.suggestionsData} keyExtractor={this.props.keyExtractor} From fb9647f2198a084b8787f352a02dd93a48cce8c5 Mon Sep 17 00:00:00 2001 From: armandgray Date: Thu, 22 Feb 2018 13:40:29 -0800 Subject: [PATCH 07/49] [#154886078] adding disableFullscreenUI={gbthis.props.disableFullscreenUI} --- src/MentionsTextInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 5a522a3..34c68dc 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -527,6 +527,7 @@ export default class MentionsTextInput extends Component { ref={component => this._textInput = component} onChangeText={this.onChangeText.bind(this)} onSelectionChange={(event) => { this.onSelectionChange(event.nativeEvent.selection); }} + disableFullscreenUI={!!this.props.disableFullscreenUI} returnKeyType={this.props.returnKeyType ? this.props.returnKeyType : 'send'} maxLength={this.props.maxLength ? this.props.maxLength : Number.MAX_SAFE_INTEGER} enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically ? this.props.enablesReturnKeyAutomatically : false} From f67065c5245ffc508adc238649a8dc9be8c3be29 Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 23 Feb 2018 15:58:29 -0800 Subject: [PATCH 08/49] [#154882915] fixing ios by making onChangeText always get called before selection handling --- src/MentionsTextInput.js | 47 +++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 34c68dc..bae6371 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -471,11 +471,7 @@ export default class MentionsTextInput extends Component { } } - onSelectionChange(selection) { - if (this.props.onSelectionChange) { - this.props.onSelectionChange(); - } - + handleSelectionChange(selection = {start: 0, end: 0}) { this.didDeleteTriggerKeyword = false; const position = selection.end - 1; @@ -493,13 +489,47 @@ export default class MentionsTextInput extends Component { this.handleReset(); } + onSelectionChange(selection) { + this.selection = selection; + this.didSelectionChange = true; + + if (this.props.onSelectionChange) { + this.props.onSelectionChange(); + } + + if (this.didTextChange) { + this.handleSelectionChange(selection); + return; + } + + let interval; + const timeout = setTimeout(() => { + if (interval) { + clearInterval(interval); + } + }, 300); + + interval = setInterval(() => { + if (this.didTextChange) { + clearInterval(interval); + clearTimeout(timeout); + handleSelectionChange(selection); + } + }, 25); + } + onChangeText(text) { + this.didTextChange = true; + this.setState({ text }, () => { + if (this.didSelectionChange) { + this.didSelectionChange = false; + this.handleSelectionChange(this.selection); + } + }); + if (this.props.onChangeText) { this.props.onChangeText(text); } - - this.didTextChange = true; - this.setState({ text }); } render() { @@ -525,6 +555,7 @@ export default class MentionsTextInput extends Component { }); }} ref={component => this._textInput = component} + platform={this.props.platform} onChangeText={this.onChangeText.bind(this)} onSelectionChange={(event) => { this.onSelectionChange(event.nativeEvent.selection); }} disableFullscreenUI={!!this.props.disableFullscreenUI} From 9ec4a3a46d191bfe7588aa039a64f89f01d78286 Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 23 Feb 2018 16:07:07 -0800 Subject: [PATCH 09/49] fixing android triggers with a direct call after onChangeText --- src/MentionsTextInput.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index bae6371..90cde49 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -490,9 +490,6 @@ export default class MentionsTextInput extends Component { } onSelectionChange(selection) { - this.selection = selection; - this.didSelectionChange = true; - if (this.props.onSelectionChange) { this.props.onSelectionChange(); } @@ -502,12 +499,15 @@ export default class MentionsTextInput extends Component { return; } + this.didSelectionChange = true; + this.selection = selection; + let interval; const timeout = setTimeout(() => { if (interval) { clearInterval(interval); } - }, 300); + }, 200); interval = setInterval(() => { if (this.didTextChange) { From b6ca6456b79c5eabbe092de5de51d53bcb6b1ab0 Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 23 Feb 2018 16:32:13 -0800 Subject: [PATCH 10/49] fixing selection change logic --- src/MentionsTextInput.js | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 90cde49..b47b1a7 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -472,6 +472,7 @@ export default class MentionsTextInput extends Component { } handleSelectionChange(selection = {start: 0, end: 0}) { + this.isSelectionChangeHandled = true; this.didDeleteTriggerKeyword = false; const position = selection.end - 1; @@ -499,30 +500,20 @@ export default class MentionsTextInput extends Component { return; } - this.didSelectionChange = true; + this.isSelectionChangeHandled = false; this.selection = selection; - let interval; - const timeout = setTimeout(() => { - if (interval) { - clearInterval(interval); - } - }, 200); - - interval = setInterval(() => { - if (this.didTextChange) { - clearInterval(interval); - clearTimeout(timeout); - handleSelectionChange(selection); + setTimeout(() => { + if (!this.isSelectionChangeHandled) { + this.handleSelectionChange(selection); } - }, 25); + }, 15); } onChangeText(text) { this.didTextChange = true; this.setState({ text }, () => { - if (this.didSelectionChange) { - this.didSelectionChange = false; + if (!this.isSelectionChangeHandled) { this.handleSelectionChange(this.selection); } }); From 508f4c7f2c834c8f854e15229e6e0513fdf11376 Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 23 Feb 2018 16:33:16 -0800 Subject: [PATCH 11/49] removing platform prop --- src/MentionsTextInput.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index b47b1a7..c124f9f 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -546,7 +546,6 @@ export default class MentionsTextInput extends Component { }); }} ref={component => this._textInput = component} - platform={this.props.platform} onChangeText={this.onChangeText.bind(this)} onSelectionChange={(event) => { this.onSelectionChange(event.nativeEvent.selection); }} disableFullscreenUI={!!this.props.disableFullscreenUI} From 2130518fe6a2202827acb719a3471cbb1ff80053 Mon Sep 17 00:00:00 2001 From: armandgray Date: Mon, 26 Feb 2018 14:28:29 -0800 Subject: [PATCH 12/49] [#154459087][#154459014] adding default value to true for isSelectionChangeHandled --- src/MentionsTextInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index c124f9f..ef7e576 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -25,6 +25,7 @@ export default class MentionsTextInput extends Component { this.lastTriggerIndex = 0; this.triggerMatrix = []; this.isTrackingStarted = false; + this.isSelectionChangeHandled = true; } componentWillMount() { From 5e8df6d6921e29ea90c2b403890af2f4688721c6 Mon Sep 17 00:00:00 2001 From: armandgray Date: Wed, 28 Feb 2018 16:48:30 -0800 Subject: [PATCH 13/49] resetting tracking on calls to resetTextbox() --- src/MentionsTextInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index ef7e576..0dc5272 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -77,6 +77,7 @@ export default class MentionsTextInput extends Component { resetTextbox() { this.triggerMatrix = []; + this.isTrackingStarted = false; this.setState({ textInputHeight: this.props.textInputMinHeight, text: '', From d87bc3a77863b67c196763091e5db6d39c692cfa Mon Sep 17 00:00:00 2001 From: armandgray Date: Wed, 28 Feb 2018 17:44:48 -0800 Subject: [PATCH 14/49] adding call to props onContentSizeChange --- src/MentionsTextInput.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 0dc5272..63bdb47 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -543,6 +543,10 @@ export default class MentionsTextInput extends Component { { + if (this.props.onContentSizeChange) { + this.props.onContentSizeChange(event); + } + this.setState({ textInputHeight: this.props.textInputMinHeight >= event.nativeEvent.contentSize.height ? this.props.textInputMinHeight : event.nativeEvent.contentSize.height + 10, }); From cc8e62898459df6884b81c8569093211d179fc57 Mon Sep 17 00:00:00 2001 From: armandgray Date: Wed, 28 Feb 2018 19:59:29 -0800 Subject: [PATCH 15/49] moving triggerCallback before opening suggestions --- src/MentionsTextInput.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 63bdb47..8c42db3 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -134,10 +134,11 @@ export default class MentionsTextInput extends Component { const keyword = this.getTriggerKeyword(position); const delay = this.props.triggerDelay ? this.props.triggerDelay : 0; if (keyword && keyword.length > delay) { - this.openSuggestionsPanel(); if (this.props.triggerCallback) { this.props.triggerCallback(keyword, this.triggerMatrix, this.getSubsequentTriggerIndex(position)); } + + this.openSuggestionsPanel(); } else { this.closeSuggestionsPanel(); } From db553e068576d04f8455a5fb73a4375975eb5228 Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 2 Mar 2018 10:55:52 -0800 Subject: [PATCH 16/49] stopping ios change text after reset --- src/MentionsTextInput.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 8c42db3..4fea8d8 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -76,11 +76,16 @@ export default class MentionsTextInput extends Component { } resetTextbox() { + this.isResetting = true; this.triggerMatrix = []; this.isTrackingStarted = false; this.setState({ textInputHeight: this.props.textInputMinHeight, text: '', + }, () => { + setTimeout(() => { + this.isResetting = false; + }, 20); }); } @@ -206,7 +211,7 @@ export default class MentionsTextInput extends Component { } isSelectionReplaced() { - return this.triggerMatrix + return this.triggerMatrix && this.triggerMatrix.length && this.state.text && this.state.text[this.triggerMatrix[this.triggerMatrix.length - 1][0]] != '@'; @@ -514,6 +519,10 @@ export default class MentionsTextInput extends Component { } onChangeText(text) { + if (this.isResetting) { + return; + } + this.didTextChange = true; this.setState({ text }, () => { if (!this.isSelectionChangeHandled) { From 268a49ed5ae194803f2dca23446251f7e7e7c9c3 Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 2 Mar 2018 11:37:47 -0800 Subject: [PATCH 17/49] requested changes --- src/MentionsTextInput.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 4fea8d8..c143b4b 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -12,6 +12,13 @@ import PropTypes from 'prop-types'; const SET_STATE_DELAY = 50; export default class MentionsTextInput extends Component { + lastTextLength: number; + lastTriggerIndex: number; + triggerMatrix: Array; + isResetting: boolean; + isTrackingStarted: boolean; + isSelectionChangeHandled: boolean; + constructor(props) { super(props); From 9fbdd766cf0b0e8425b2f9835401278df0ccd7e1 Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Wed, 7 Mar 2018 13:37:12 -0800 Subject: [PATCH 18/49] [#155044277] added accessibility inside text input instead of outer views --- src/MentionsTextInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index c143b4b..d68ee9f 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -569,6 +569,7 @@ export default class MentionsTextInput extends Component { }); }} ref={component => this._textInput = component} + accessibilityLabel={ 'chat_text_input_box' } onChangeText={this.onChangeText.bind(this)} onSelectionChange={(event) => { this.onSelectionChange(event.nativeEvent.selection); }} disableFullscreenUI={!!this.props.disableFullscreenUI} From 39b34209a08686266181178ab6b2b87b0b0ade61 Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Wed, 7 Mar 2018 13:57:26 -0800 Subject: [PATCH 19/49] rename to test --- src/MentionsTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index d68ee9f..f2977ea 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -569,7 +569,7 @@ export default class MentionsTextInput extends Component { }); }} ref={component => this._textInput = component} - accessibilityLabel={ 'chat_text_input_box' } + accessibilityLabel={ 'chat_text_input' } onChangeText={this.onChangeText.bind(this)} onSelectionChange={(event) => { this.onSelectionChange(event.nativeEvent.selection); }} disableFullscreenUI={!!this.props.disableFullscreenUI} From 00e73c0962100272544eee60f0e2219b7abd87aa Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Wed, 7 Mar 2018 14:14:40 -0800 Subject: [PATCH 20/49] made changes to accommodate Jake's request --- src/MentionsTextInput.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index f2977ea..e77aefb 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -545,7 +545,8 @@ export default class MentionsTextInput extends Component { render() { return ( - + Loading..., + loadingComponent: () => Loading..., textInputMinHeight: 30, textInputMaxHeight: 80, horizontal: true, From e731a265880cf815e1330734c86010fcf083c8a8 Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Wed, 7 Mar 2018 14:27:53 -0800 Subject: [PATCH 21/49] test --- src/MentionsTextInput.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index e77aefb..cd7764f 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -549,6 +549,7 @@ export default class MentionsTextInput extends Component { accessible={false}> { return this.props.ItemSeparatorComponent ? this.props.ItemSeparatorComponent() : }} From 965ad3d1378c50296ae8d9cd68531ceec0a3aea1 Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Wed, 7 Mar 2018 14:40:47 -0800 Subject: [PATCH 22/49] removed accessible to false here --- src/MentionsTextInput.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index cd7764f..e77aefb 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -549,7 +549,6 @@ export default class MentionsTextInput extends Component { accessible={false}> { return this.props.ItemSeparatorComponent ? this.props.ItemSeparatorComponent() : }} From 78ca87d562f0dd9ea099051a78ff650b818e5a20 Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Wed, 7 Mar 2018 14:41:08 -0800 Subject: [PATCH 23/49] change name to see changes --- src/MentionsTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index e77aefb..b2cfdea 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -570,7 +570,7 @@ export default class MentionsTextInput extends Component { }); }} ref={component => this._textInput = component} - accessibilityLabel={ 'chat_text_input' } + accessibilityLabel={ 'chat_input_text' } onChangeText={this.onChangeText.bind(this)} onSelectionChange={(event) => { this.onSelectionChange(event.nativeEvent.selection); }} disableFullscreenUI={!!this.props.disableFullscreenUI} From 72755493184f4b3b51b334bc76ee5435d4cc09e3 Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 9 Mar 2018 11:34:03 -0800 Subject: [PATCH 24/49] making suggestions case insensitive --- src/MentionsTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index c143b4b..059ddfd 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -248,7 +248,7 @@ export default class MentionsTextInput extends Component { const start = this.triggerMatrix[index][0]; const end = this.triggerMatrix[index][1]; - const pattern = new RegExp(`${this.props.trigger}[a-z0-9_-]*`, `gi`); + const pattern = new RegExp(`${this.props.trigger}[a-zA-Z0-9_-]*`, `gi`); const triggerText = this.state.text.slice(start, end + this.getDistanceToNextSpace(end) + 1); const keywordArray = triggerText.match(pattern); From fb415ec5dc50f265357936468c33e183a4d523ae Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Tue, 13 Mar 2018 14:38:44 -0700 Subject: [PATCH 25/49] [#155867191] editable prop is always passed down so just set to whatever the prop is. The issue at hand was that this comparison checks if its true then set it true, if its false, also set it to true. --- src/MentionsTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index b2cfdea..c8f00cb 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -578,7 +578,7 @@ export default class MentionsTextInput extends Component { maxLength={this.props.maxLength ? this.props.maxLength : Number.MAX_SAFE_INTEGER} enablesReturnKeyAutomatically={this.props.enablesReturnKeyAutomatically ? this.props.enablesReturnKeyAutomatically : false} underlineColorAndroid={this.props.underlineColorAndroid ? this.props.underlineColorAndroid : 'black'} - editable={this.props.editable ? this.props.editable : true} + editable={this.props.editable} onFocus={ () => {if (this.props.onFocus) {this.props.onFocus();}} } onBlur={ () => {if (this.props.onBlur) {this.props.onBlur();}} } multiline={true} From b4b50c7dcecdf10f040aed4de53ba10ec2fd2a46 Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Tue, 13 Mar 2018 15:28:39 -0700 Subject: [PATCH 26/49] added default prop --- src/MentionsTextInput.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index c8f00cb..4a8bdbc 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -19,6 +19,14 @@ export default class MentionsTextInput extends Component { isTrackingStarted: boolean; isSelectionChangeHandled: boolean; + static propTypes = { + editable: PropTypes.bool, + }; + + static defaultProps = { + editable: true + }; + constructor(props) { super(props); From 94239d47ccbf3612a0cfae371bc50d29e46b68fb Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Tue, 13 Mar 2018 15:34:11 -0700 Subject: [PATCH 27/49] made requested changes --- src/MentionsTextInput.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 4a8bdbc..cddbaae 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -19,14 +19,6 @@ export default class MentionsTextInput extends Component { isTrackingStarted: boolean; isSelectionChangeHandled: boolean; - static propTypes = { - editable: PropTypes.bool, - }; - - static defaultProps = { - editable: true - }; - constructor(props) { super(props); @@ -607,6 +599,7 @@ MentionsTextInput.propTypes = { PropTypes.func, PropTypes.element, ]), + editable: PropTypes.bool, textInputMinHeight: PropTypes.number, textInputMaxHeight: PropTypes.number, trigger: PropTypes.string.isRequired, @@ -638,4 +631,5 @@ MentionsTextInput.defaultProps = { textInputMinHeight: 30, textInputMaxHeight: 80, horizontal: true, + editable: true, } From 9f2083fe7cf80cb81fe86a58b8b73b332611e126 Mon Sep 17 00:00:00 2001 From: armandgray Date: Wed, 14 Mar 2018 17:34:36 -0700 Subject: [PATCH 28/49] [#155998401][#155998606] fixing android & ios send on enter to work and not show new line --- src/MentionsTextInput.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 8a19562..6b4e330 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -530,6 +530,11 @@ export default class MentionsTextInput extends Component { return; } + if (text && text.length > 0 && text[text.length - 1] == '\n') { + this.props.onKeyPress({ nativeEvent: { key: "Enter" } }); + return; + } + this.didTextChange = true; this.setState({ text }, () => { if (!this.isSelectionChangeHandled) { From 5c906fe2a936e1fb4ef6a7cad216a73c2c6ad5ed Mon Sep 17 00:00:00 2001 From: armandgray Date: Wed, 14 Mar 2018 18:27:04 -0700 Subject: [PATCH 29/49] [#155997991] reloading trigger matrix when text is pasted in to input bar --- src/MentionsTextInput.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 6b4e330..2e634e0 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -535,6 +535,17 @@ export default class MentionsTextInput extends Component { return; } + const isTextDifferenceGreaterThanOne = !this.state.text && text.length > 1 || this.state.text.length < text.length - 1; + if (isTextDifferenceGreaterThanOne) { + // reset triggerMatrix for pasted text + this.reloadTriggerMatrix(text); + if (this.triggerMatrix.length > 0) { + const trigger = this.triggerMatrix[this.triggerMatrix.length - 1]; + const keyword = text.slice(trigger[0], trigger[1] + 1); + this.props.triggerCallback(keyword, this.triggerMatrix, this.triggerMatrix.length - 1); + } + } + this.didTextChange = true; this.setState({ text }, () => { if (!this.isSelectionChangeHandled) { From 0947e9b95fac2dc048c18661f3aba4230480a17f Mon Sep 17 00:00:00 2001 From: armandgray Date: Thu, 15 Mar 2018 09:58:52 -0700 Subject: [PATCH 30/49] moving isTextDifferenceGreaterThanOne to separate function --- src/MentionsTextInput.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 2e634e0..6df3aaf 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -525,6 +525,10 @@ export default class MentionsTextInput extends Component { }, 15); } + isTextDifferenceGreaterThanOne(text1, text2) { + return !text1 && text2.length > 1 || text1.length < text2.length - 1; + } + onChangeText(text) { if (this.isResetting) { return; @@ -535,8 +539,7 @@ export default class MentionsTextInput extends Component { return; } - const isTextDifferenceGreaterThanOne = !this.state.text && text.length > 1 || this.state.text.length < text.length - 1; - if (isTextDifferenceGreaterThanOne) { + if (this.isTextDifferenceGreaterThanOne(this.state.text, text)) { // reset triggerMatrix for pasted text this.reloadTriggerMatrix(text); if (this.triggerMatrix.length > 0) { From df2f7185f1a70f5260fa3bfaac4c9b75af551006 Mon Sep 17 00:00:00 2001 From: armandgray Date: Mon, 19 Mar 2018 11:04:15 -0700 Subject: [PATCH 31/49] [#156099305] fixing ios paste text into chat bar --- src/MentionsTextInput.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 6df3aaf..8f46320 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -63,10 +63,6 @@ export default class MentionsTextInput extends Component { } componentWillReceiveProps(nextProps) { - if (!nextProps.value) { - this.resetTextbox(); - } - setTimeout(() => { if (this.isTrackingStarted && nextProps.didPressSuggestion && nextProps.value != this.state.text && !this.didDeleteTriggerKeyword) { this.reloadTriggerMatrix(nextProps.value); From ed33adaa231ae1f51aeacc2550159695c071cab7 Mon Sep 17 00:00:00 2001 From: armandgray Date: Mon, 19 Mar 2018 16:42:54 -0700 Subject: [PATCH 32/49] [#156100300] fixing initially padding and padding updates for Pixel --- src/MentionsTextInput.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 8f46320..6cf11ab 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -557,6 +557,19 @@ export default class MentionsTextInput extends Component { } } + onContentSizeChange(event) { + if (this.props.onContentSizeChange) { + this.props.onContentSizeChange(event); + } + + const singleLineThreshold = this.props.platform == 'android' ? 12 : 0; + const heightDifference = event.nativeEvent.contentSize.height - this.props.textInputMinHeight; + const newHeight = event.nativeEvent.contentSize.height + 10; + const height = heightDifference <= singleLineThreshold ? this.props.textInputMinHeight : newHeight; + + this.setState({ textInputHeight: height }); + } + render() { return ( @@ -575,15 +588,7 @@ export default class MentionsTextInput extends Component { { - if (this.props.onContentSizeChange) { - this.props.onContentSizeChange(event); - } - - this.setState({ - textInputHeight: this.props.textInputMinHeight >= event.nativeEvent.contentSize.height ? this.props.textInputMinHeight : event.nativeEvent.contentSize.height + 10, - }); - }} + onContentSizeChange={this.onContentSizeChange.bind(this)} ref={component => this._textInput = component} accessibilityLabel={ 'chat_input_text' } onChangeText={this.onChangeText.bind(this)} @@ -621,6 +626,8 @@ MentionsTextInput.propTypes = { triggerLocation: PropTypes.oneOf(['new-word-only', 'anywhere']).isRequired, value: PropTypes.string.isRequired, onChangeText: PropTypes.func.isRequired, + placeholder: PropTypes.string, + platform: PropTypes.string, triggerCallback: PropTypes.func.isRequired, renderSuggestionsRow: PropTypes.oneOfType([ PropTypes.func, From 39510a14c11d112e19931cc8c863b414d571d7ce Mon Sep 17 00:00:00 2001 From: armandgray Date: Mon, 19 Mar 2018 16:54:44 -0700 Subject: [PATCH 33/49] [#156100300] fixing singleLineThreshold reset after deleting back to 1 line --- src/MentionsTextInput.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 6cf11ab..1c848b0 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -558,16 +558,17 @@ export default class MentionsTextInput extends Component { } onContentSizeChange(event) { - if (this.props.onContentSizeChange) { - this.props.onContentSizeChange(event); - } - const singleLineThreshold = this.props.platform == 'android' ? 12 : 0; const heightDifference = event.nativeEvent.contentSize.height - this.props.textInputMinHeight; const newHeight = event.nativeEvent.contentSize.height + 10; const height = heightDifference <= singleLineThreshold ? this.props.textInputMinHeight : newHeight; this.setState({ textInputHeight: height }); + + if (this.props.onContentSizeChange) { + event.nativeEvent.contentSize.height = height; + this.props.onContentSizeChange(event); + } } render() { From f9978dcce0e3e4973bad33376571478ad8f84eab Mon Sep 17 00:00:00 2001 From: armandgray Date: Tue, 20 Mar 2018 10:10:26 -0700 Subject: [PATCH 34/49] removing platform prop --- src/MentionsTextInput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 1c848b0..1350ffa 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -4,6 +4,7 @@ import { View, Animated, TextInput, + Platform, FlatList, ViewPropTypes } from 'react-native'; @@ -558,7 +559,7 @@ export default class MentionsTextInput extends Component { } onContentSizeChange(event) { - const singleLineThreshold = this.props.platform == 'android' ? 12 : 0; + const singleLineThreshold = Platform.OS == 'android' ? 12 : 0; const heightDifference = event.nativeEvent.contentSize.height - this.props.textInputMinHeight; const newHeight = event.nativeEvent.contentSize.height + 10; const height = heightDifference <= singleLineThreshold ? this.props.textInputMinHeight : newHeight; @@ -628,7 +629,6 @@ MentionsTextInput.propTypes = { value: PropTypes.string.isRequired, onChangeText: PropTypes.func.isRequired, placeholder: PropTypes.string, - platform: PropTypes.string, triggerCallback: PropTypes.func.isRequired, renderSuggestionsRow: PropTypes.oneOfType([ PropTypes.func, From 6b90bcfc9efc04d786dab7d452efe7bd694df277 Mon Sep 17 00:00:00 2001 From: armandgray Date: Thu, 22 Mar 2018 11:00:21 -0700 Subject: [PATCH 35/49] =?UTF-8?q?[#156101143]=20after=20deleting=20an=20@?= =?UTF-8?q?=20mention=20moving=20the=20cursor=20to=20right=20after=20the?= =?UTF-8?q?=20=E2=80=9C@=E2=80=9D=20symbol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MentionsTextInput.js | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 1350ffa..e6d857c 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -27,6 +27,7 @@ export default class MentionsTextInput extends Component { textInputHeight: '', suggestionRowHeight: new Animated.Value(0), text: this.props.value ? this.props.value : '', + selection: { start: 0, end: 0 }, }; this.lastTextLength = 0; @@ -371,6 +372,27 @@ export default class MentionsTextInput extends Component { } } + setSelection(start, end = start) { + if (this._textInput) { + this._textInput.focus(); + this.setState({ selection: { start, end } }); + } + } + + updateStateForDeletedTrigger(text, selectionIndex) { + this.didTextChange = true; + this.didDeleteTriggerKeyword = true; + this.shouldDeleteTriggerOnBackspace = false; + this.handleTriggerMatrixShiftLeft(selectionIndex - 1, this.getSubsequentTriggerIndex(selectionIndex), 1); + + this.setState({ + text: text, + }, () => { + this.setSelection(selectionIndex + 1); + this.startTracking(selectionIndex); + }); + } + deleteTriggerKeyword(index, addSpace) { const start = this.triggerMatrix[index][0]; const end = this.triggerMatrix[index][1]; @@ -382,17 +404,12 @@ export default class MentionsTextInput extends Component { const preTriggerText = this.state.text.slice(0, start + 1); const postTriggerText = this.state.text.slice(end, this.state.text.length); const space = postTriggerText.length && addSpace ? ' ' : ''; + const text = preTriggerText + space + postTriggerText; this.handleTriggerDeletion(index); setTimeout(() => { - this.didTextChange = true; - this.didDeleteTriggerKeyword = true; - this.shouldDeleteTriggerOnBackspace = false; - this.handleTriggerMatrixShiftLeft(start - 1, this.getSubsequentTriggerIndex(start), 1); - this.setState({ text: preTriggerText + space + postTriggerText }, () => { - this.startTracking(start); - }); + this.updateStateForDeletedTrigger(text, start); }, SET_STATE_DELAY); } @@ -507,6 +524,10 @@ export default class MentionsTextInput extends Component { this.props.onSelectionChange(); } + if (!this.didDeleteTriggerKeyword) { + this.setState({ selection }); + } + if (this.didTextChange) { this.handleSelectionChange(selection); return; @@ -593,6 +614,7 @@ export default class MentionsTextInput extends Component { onContentSizeChange={this.onContentSizeChange.bind(this)} ref={component => this._textInput = component} accessibilityLabel={ 'chat_input_text' } + selection={ this.state.selection } onChangeText={this.onChangeText.bind(this)} onSelectionChange={(event) => { this.onSelectionChange(event.nativeEvent.selection); }} disableFullscreenUI={!!this.props.disableFullscreenUI} From ab2aec47733e2cd75662091146066bade1c1cdff Mon Sep 17 00:00:00 2001 From: armandgray Date: Thu, 22 Mar 2018 16:40:26 -0700 Subject: [PATCH 36/49] [#156101143] completing initial setup for moving cursor on deletes --- src/MentionsTextInput.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index e6d857c..8b9c807 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -27,7 +27,7 @@ export default class MentionsTextInput extends Component { textInputHeight: '', suggestionRowHeight: new Animated.Value(0), text: this.props.value ? this.props.value : '', - selection: { start: 0, end: 0 }, + selection: null, }; this.lastTextLength = 0; @@ -385,6 +385,10 @@ export default class MentionsTextInput extends Component { this.shouldDeleteTriggerOnBackspace = false; this.handleTriggerMatrixShiftLeft(selectionIndex - 1, this.getSubsequentTriggerIndex(selectionIndex), 1); + if (this.props.onChangeText) { + this.props.onChangeText(text); + } + this.setState({ text: text, }, () => { @@ -500,7 +504,17 @@ export default class MentionsTextInput extends Component { } } - handleSelectionChange(selection = {start: 0, end: 0}) { + handleSelectionStateReset() { + if (this.state.selection && this.shouldResetSelectionState) { + this.setState({ selection: null }); + } + + this.shouldResetSelectionState = this.didDeleteTriggerKeyword && !!this.state.selection; + } + + handleSelectionChange(selection) { + this.handleSelectionStateReset(); + this.isSelectionChangeHandled = true; this.didDeleteTriggerKeyword = false; @@ -524,10 +538,6 @@ export default class MentionsTextInput extends Component { this.props.onSelectionChange(); } - if (!this.didDeleteTriggerKeyword) { - this.setState({ selection }); - } - if (this.didTextChange) { this.handleSelectionChange(selection); return; From 8cc3932a524874783927a34df684e90be25d792f Mon Sep 17 00:00:00 2001 From: armandgray Date: Thu, 22 Mar 2018 17:31:44 -0700 Subject: [PATCH 37/49] [#156213326] sending on enter when clicked anywhere in the message --- src/MentionsTextInput.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 8b9c807..df234d5 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -557,12 +557,17 @@ export default class MentionsTextInput extends Component { return !text1 && text2.length > 1 || text1.length < text2.length - 1; } + hasNewLineChar(text) { + return text && text.length > 0 && text[text.length - 1] == '\n' + || text.indexOf('\n') !== -1; + } + onChangeText(text) { if (this.isResetting) { return; } - if (text && text.length > 0 && text[text.length - 1] == '\n') { + if (this.hasNewLineChar(text)) { this.props.onKeyPress({ nativeEvent: { key: "Enter" } }); return; } From f6b17723ff14e15ab7163836e818e6336306cc98 Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 23 Mar 2018 14:23:09 -0700 Subject: [PATCH 38/49] fixing android selection updates --- src/MentionsTextInput.js | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index df234d5..b2381ff 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -38,9 +38,7 @@ export default class MentionsTextInput extends Component { } componentWillMount() { - this.setState({ - textInputHeight: this.props.textInputMinHeight - }); + this.setState({ textInputHeight: this.props.textInputMinHeight }); } reloadTriggerMatrix(text) { @@ -504,16 +502,31 @@ export default class MentionsTextInput extends Component { } } - handleSelectionStateReset() { - if (this.state.selection && this.shouldResetSelectionState) { - this.setState({ selection: null }); - } + wasDeletionFromIndexOne() { + const lastSelection = this.state.selection; + return lastSelection && lastSelection.start == 1 && this.getTextDifference() == 1; + } - this.shouldResetSelectionState = this.didDeleteTriggerKeyword && !!this.state.selection; + isZeroWithText(selection) { + return Platform.OS == 'ios' && selection.start == 0 && selection.start == selection.end + && this.state.text.length != 0; + } + + isUnchangedSelectionState(selection) { + return this.state.selection && selection && selection.start == this.state.selection.start + && selection.end == this.state.selection.end; + } + + shouldUpdateSelectionState(selection) { + return selection && this.isUnchangedSelectionState(selection) && !this.didDeleteTriggerKeyword + && selection.start <= this.state.text.length + && (!this.isZeroWithText(selection) || this.wasDeletionFromIndexOne()); } handleSelectionChange(selection) { - this.handleSelectionStateReset(); + if (this.shouldUpdateSelectionState(selection)) { + this.setSelection(selection.start, selection.end); + } this.isSelectionChangeHandled = true; this.didDeleteTriggerKeyword = false; From d2f3fff9efde47791f8a795807d169572a8f8bdb Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 23 Mar 2018 14:50:43 -0700 Subject: [PATCH 39/49] fixing ios cursor jumping around --- src/MentionsTextInput.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index b2381ff..889fc3b 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -502,9 +502,8 @@ export default class MentionsTextInput extends Component { } } - wasDeletionFromIndexOne() { - const lastSelection = this.state.selection; - return lastSelection && lastSelection.start == 1 && this.getTextDifference() == 1; + wasLastSelectionIndexOne() { + return this.state.selection && this.state.selection.start == 1; } isZeroWithText(selection) { @@ -518,9 +517,9 @@ export default class MentionsTextInput extends Component { } shouldUpdateSelectionState(selection) { - return selection && this.isUnchangedSelectionState(selection) && !this.didDeleteTriggerKeyword + return selection && this.state.text && !this.isUnchangedSelectionState(selection) && !this.didDeleteTriggerKeyword && selection.start <= this.state.text.length - && (!this.isZeroWithText(selection) || this.wasDeletionFromIndexOne()); + && (!this.isZeroWithText(selection) || this.wasLastSelectionIndexOne()); } handleSelectionChange(selection) { @@ -642,7 +641,7 @@ export default class MentionsTextInput extends Component { onContentSizeChange={this.onContentSizeChange.bind(this)} ref={component => this._textInput = component} accessibilityLabel={ 'chat_input_text' } - selection={ this.state.selection } + selection={ this.state.text && this.state.text.length > 0 ? this.state.selection : { start: 0, end: 0 } } onChangeText={this.onChangeText.bind(this)} onSelectionChange={(event) => { this.onSelectionChange(event.nativeEvent.selection); }} disableFullscreenUI={!!this.props.disableFullscreenUI} From a870479b7882d4d211dd81670663d27e366a4c2a Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 23 Mar 2018 14:54:20 -0700 Subject: [PATCH 40/49] setting initial state to 0 --- src/MentionsTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 889fc3b..7c6620e 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -27,7 +27,7 @@ export default class MentionsTextInput extends Component { textInputHeight: '', suggestionRowHeight: new Animated.Value(0), text: this.props.value ? this.props.value : '', - selection: null, + selection: { start: 0, end: 0 }, }; this.lastTextLength = 0; From 4771e908ea0f5b23c9525a641e0bc1292060f869 Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 23 Mar 2018 15:06:10 -0700 Subject: [PATCH 41/49] requested changes --- src/MentionsTextInput.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 7c6620e..a968a77 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -570,8 +570,7 @@ export default class MentionsTextInput extends Component { } hasNewLineChar(text) { - return text && text.length > 0 && text[text.length - 1] == '\n' - || text.indexOf('\n') !== -1; + return text && text.length > 0 && text.indexOf('\n') !== -1; } onChangeText(text) { From 7ddc14c35fa2ef88721c6c857a225b33d4288836 Mon Sep 17 00:00:00 2001 From: armandgray Date: Mon, 26 Mar 2018 14:18:18 -0700 Subject: [PATCH 42/49] [#156258192] reverting selection update changes --- src/MentionsTextInput.js | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index a968a77..3137fcb 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -27,7 +27,6 @@ export default class MentionsTextInput extends Component { textInputHeight: '', suggestionRowHeight: new Animated.Value(0), text: this.props.value ? this.props.value : '', - selection: { start: 0, end: 0 }, }; this.lastTextLength = 0; @@ -370,13 +369,6 @@ export default class MentionsTextInput extends Component { } } - setSelection(start, end = start) { - if (this._textInput) { - this._textInput.focus(); - this.setState({ selection: { start, end } }); - } - } - updateStateForDeletedTrigger(text, selectionIndex) { this.didTextChange = true; this.didDeleteTriggerKeyword = true; @@ -390,7 +382,6 @@ export default class MentionsTextInput extends Component { this.setState({ text: text, }, () => { - this.setSelection(selectionIndex + 1); this.startTracking(selectionIndex); }); } @@ -502,31 +493,7 @@ export default class MentionsTextInput extends Component { } } - wasLastSelectionIndexOne() { - return this.state.selection && this.state.selection.start == 1; - } - - isZeroWithText(selection) { - return Platform.OS == 'ios' && selection.start == 0 && selection.start == selection.end - && this.state.text.length != 0; - } - - isUnchangedSelectionState(selection) { - return this.state.selection && selection && selection.start == this.state.selection.start - && selection.end == this.state.selection.end; - } - - shouldUpdateSelectionState(selection) { - return selection && this.state.text && !this.isUnchangedSelectionState(selection) && !this.didDeleteTriggerKeyword - && selection.start <= this.state.text.length - && (!this.isZeroWithText(selection) || this.wasLastSelectionIndexOne()); - } - handleSelectionChange(selection) { - if (this.shouldUpdateSelectionState(selection)) { - this.setSelection(selection.start, selection.end); - } - this.isSelectionChangeHandled = true; this.didDeleteTriggerKeyword = false; @@ -640,7 +607,6 @@ export default class MentionsTextInput extends Component { onContentSizeChange={this.onContentSizeChange.bind(this)} ref={component => this._textInput = component} accessibilityLabel={ 'chat_input_text' } - selection={ this.state.text && this.state.text.length > 0 ? this.state.selection : { start: 0, end: 0 } } onChangeText={this.onChangeText.bind(this)} onSelectionChange={(event) => { this.onSelectionChange(event.nativeEvent.selection); }} disableFullscreenUI={!!this.props.disableFullscreenUI} From 96cd1752cf93de77a7cb00f9b131cd0f7ac1cc43 Mon Sep 17 00:00:00 2001 From: armandgray Date: Mon, 26 Mar 2018 14:52:59 -0700 Subject: [PATCH 43/49] [#156258192] fixing weird gray bar that sometimes appears above the suggestions on android --- src/MentionsTextInput.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 3137fcb..1211699 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -99,20 +99,22 @@ export default class MentionsTextInput extends Component { } openSuggestionsPanel() { - if (this.props.onOpenSuggestionsPanel) { - this.props.onOpenSuggestionsPanel(); - } - let numOfRows = 0; if (this.props.suggestionsData) { const isDataLengthBelowMax = this.props.MaxVisibleRowCount >= this.props.suggestionsData.length; numOfRows = isDataLengthBelowMax ? this.props.suggestionsData.length : this.props.MaxVisibleRowCount; } - Animated.timing(this.state.suggestionRowHeight, { - duration: 0, - toValue: numOfRows * this.props.suggestionRowHeight, - }).start(); + if (numOfRows != 0 && this.props.onOpenSuggestionsPanel) { + this.props.onOpenSuggestionsPanel(); + } + + if (numOfRows != this.state.suggestionRowHeight) { + Animated.timing(this.state.suggestionRowHeight, { + duration: 0, + toValue: numOfRows * this.props.suggestionRowHeight, + }).start(); + } } closeSuggestionsPanel() { From 09d7f94336b535e1b565c811378cf4790ecd131c Mon Sep 17 00:00:00 2001 From: armandgray Date: Mon, 2 Apr 2018 19:19:50 -0700 Subject: [PATCH 44/49] [#156455025] fixing duplicate text after sending mention only by readding didPropsChangeText --- src/MentionsTextInput.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 1211699..7701477 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -79,6 +79,7 @@ export default class MentionsTextInput extends Component { resetTextbox() { this.isResetting = true; + this.didPropsChangeText = true; this.triggerMatrix = []; this.isTrackingStarted = false; this.setState({ @@ -471,6 +472,7 @@ export default class MentionsTextInput extends Component { const lastChar = this.state.text[position]; const wordBoundary = (this.props.triggerLocation === 'new-word-only') ? position === 0 || this.state.text[position - 1] === ' ' : true; + this.handleTriggerMatrixChanges(position); this.handleDeleteTriggerOnBackspace(position); From 93bffdaee0067dc9b05100ac26398ab9af7f4b98 Mon Sep 17 00:00:00 2001 From: armandgray Date: Mon, 2 Apr 2018 19:20:34 -0700 Subject: [PATCH 45/49] removing space --- src/MentionsTextInput.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 7701477..79e6c7e 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -472,7 +472,6 @@ export default class MentionsTextInput extends Component { const lastChar = this.state.text[position]; const wordBoundary = (this.props.triggerLocation === 'new-word-only') ? position === 0 || this.state.text[position - 1] === ' ' : true; - this.handleTriggerMatrixChanges(position); this.handleDeleteTriggerOnBackspace(position); From 1d9b1cdc92ba7b204467d7b9db272c0b92fe113d Mon Sep 17 00:00:00 2001 From: Emily Beckers Date: Mon, 16 Jul 2018 13:49:22 -0700 Subject: [PATCH 46/49] [#156489123] fixed how trigger is called when pasting or autocorrect firing --- src/MentionsTextInput.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 79e6c7e..cdd5a6d 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -34,6 +34,7 @@ export default class MentionsTextInput extends Component { this.triggerMatrix = []; this.isTrackingStarted = false; this.isSelectionChangeHandled = true; + this.selection = {}; } componentWillMount() { @@ -515,9 +516,25 @@ export default class MentionsTextInput extends Component { this.handleReset(); } + handleTriggeringForPaste(text) { + let keyword = null; + let index = null; + if (this.triggerMatrix.length && this.selection.start == this.selection.end) { + this.triggerMatrix.forEach((points, i) => { + // cursor is inside keyword when it is from after the trigger character to touching the end of the word + if (this.selection.end > points[0] && this.selection.end <= points[1] + 1) { + keyword = text.slice(points[0], points[1] + 1); + index = i; + } + }); + } + + this.props.triggerCallback(keyword, this.triggerMatrix, index); + } + onSelectionChange(selection) { if (this.props.onSelectionChange) { - this.props.onSelectionChange(); + this.props.onSelectionChange(selection); } if (this.didTextChange) { @@ -554,12 +571,10 @@ export default class MentionsTextInput extends Component { } if (this.isTextDifferenceGreaterThanOne(this.state.text, text)) { - // reset triggerMatrix for pasted text + // reset triggerMatrix for pasted text/autocorrect this.reloadTriggerMatrix(text); if (this.triggerMatrix.length > 0) { - const trigger = this.triggerMatrix[this.triggerMatrix.length - 1]; - const keyword = text.slice(trigger[0], trigger[1] + 1); - this.props.triggerCallback(keyword, this.triggerMatrix, this.triggerMatrix.length - 1); + this.handleTriggeringForPaste(text); } } From d89e2c029af06587865a100a583c1b39d38d9c8a Mon Sep 17 00:00:00 2001 From: armandgray Date: Mon, 23 Jul 2018 12:57:08 -0700 Subject: [PATCH 47/49] [#156101143] Adding setSelection to move cursor & fixing cursor position and suggestions display on deletions --- src/MentionsTextInput.js | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index cdd5a6d..7289957 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -10,7 +10,7 @@ import { } from 'react-native'; import PropTypes from 'prop-types'; -const SET_STATE_DELAY = 50; +const SET_STATE_DELAY = 75; export default class MentionsTextInput extends Component { lastTextLength: number; @@ -35,6 +35,7 @@ export default class MentionsTextInput extends Component { this.isTrackingStarted = false; this.isSelectionChangeHandled = true; this.selection = {}; + this.setSelection = {}; } componentWillMount() { @@ -66,18 +67,23 @@ export default class MentionsTextInput extends Component { setTimeout(() => { if (this.isTrackingStarted && nextProps.didPressSuggestion && nextProps.value != this.state.text && !this.didDeleteTriggerKeyword) { this.reloadTriggerMatrix(nextProps.value); - if (this.props.triggerCallback && this.triggerMatrix) { - const keyword = this.triggerMatrix.length ? this.triggerMatrix[this.triggerMatrix.length - 1] : null; - const index = this.triggerMatrix.length ? this.triggerMatrix.length - 1 : null; - this.props.triggerCallback(keyword, this.triggerMatrix, index); - } - this.stopTracking(); - this.setState({ text: nextProps.value }); + this.setState({ text: nextProps.value }, () => { + this.setCursorPosition(this.triggerMatrix[this.lastTriggerIndex][1] + 1); + }); } }, SET_STATE_DELAY); } + setCursorPosition(position: number) { + setTimeout(() => { + const index = position + (position == this.state.text.length ? 0 : 1); + this.setSelection = { start: index, end: index }; + this.forceUpdate(); + this.setSelection = {}; + }, SET_STATE_DELAY); + } + resetTextbox() { this.isResetting = true; this.didPropsChangeText = true; @@ -165,10 +171,11 @@ export default class MentionsTextInput extends Component { } const isAtEnd = position === this.state.text.length - 1; + const isTriggerSymbolOnly = this.triggerMatrix[index][0] === this.triggerMatrix[index][1]; const isAtEndOfTrigger = this.triggerMatrix[index][1] === position; const isFollowedBySpace = this.state.text[position + 1] === ' '; - this.shouldDeleteTriggerOnBackspace = isAtEndOfTrigger && (isAtEnd || isFollowedBySpace); + this.shouldDeleteTriggerOnBackspace = !isTriggerSymbolOnly && isAtEndOfTrigger && (isAtEnd || isFollowedBySpace); } handleClick(position) { @@ -383,9 +390,8 @@ export default class MentionsTextInput extends Component { this.props.onChangeText(text); } - this.setState({ - text: text, - }, () => { + this.setState({ text: text }, () => { + this.setCursorPosition(selectionIndex); this.startTracking(selectionIndex); }); } @@ -533,6 +539,8 @@ export default class MentionsTextInput extends Component { } onSelectionChange(selection) { + this.selection = {}; + this.setSelection = {}; if (this.props.onSelectionChange) { this.props.onSelectionChange(selection); } @@ -636,6 +644,7 @@ export default class MentionsTextInput extends Component { onFocus={ () => {if (this.props.onFocus) {this.props.onFocus();}} } onBlur={ () => {if (this.props.onBlur) {this.props.onBlur();}} } multiline={true} + selection={this.setSelection} value={this.state.text} style={[{ ...this.props.textInputStyle }, { height: Math.min(this.props.textInputMaxHeight, this.state.textInputHeight) }]} placeholder={this.props.placeholder ? this.props.placeholder : 'Write a comment...'} From 0522d0866844f0875f2307dec76475f79a1fbb32 Mon Sep 17 00:00:00 2001 From: armandgray Date: Fri, 27 Jul 2018 14:22:43 -0700 Subject: [PATCH 48/49] Adding Platform check to restrict most recent updates to only Android --- src/MentionsTextInput.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 7289957..7d8893d 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -76,6 +76,10 @@ export default class MentionsTextInput extends Component { } setCursorPosition(position: number) { + if (Platform.OS == 'ios') { + return; + } + setTimeout(() => { const index = position + (position == this.state.text.length ? 0 : 1); this.setSelection = { start: index, end: index }; @@ -644,7 +648,7 @@ export default class MentionsTextInput extends Component { onFocus={ () => {if (this.props.onFocus) {this.props.onFocus();}} } onBlur={ () => {if (this.props.onBlur) {this.props.onBlur();}} } multiline={true} - selection={this.setSelection} + selection={Platform.OS == 'android' ? this.setSelection : undefined} value={this.state.text} style={[{ ...this.props.textInputStyle }, { height: Math.min(this.props.textInputMaxHeight, this.state.textInputHeight) }]} placeholder={this.props.placeholder ? this.props.placeholder : 'Write a comment...'} From 2f3454d64ae9d99bfbb92a1024776e1db3a029af Mon Sep 17 00:00:00 2001 From: Melina Young <> Date: Tue, 2 Oct 2018 16:49:36 -0700 Subject: [PATCH 49/49] [#160866889] the placeholder on the ota side will be "" so that the accessibility label won't have any extra white space --- src/MentionsTextInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MentionsTextInput.js b/src/MentionsTextInput.js index 7d8893d..bbe32e4 100644 --- a/src/MentionsTextInput.js +++ b/src/MentionsTextInput.js @@ -651,7 +651,7 @@ export default class MentionsTextInput extends Component { selection={Platform.OS == 'android' ? this.setSelection : undefined} value={this.state.text} style={[{ ...this.props.textInputStyle }, { height: Math.min(this.props.textInputMaxHeight, this.state.textInputHeight) }]} - placeholder={this.props.placeholder ? this.props.placeholder : 'Write a comment...'} + placeholder={this.props.placeholder} onKeyPress={(e) => { if (this.props.onKeyPress) {this.props.onKeyPress(e);} }} />