Skip to content

Commit b4a27c9

Browse files
authored
Feat: added the new prop scrollToItem (#76)
I've completely redesigned scrollTo behaviour #70 From this very moment, you can control the behavior with newly introduced prop `scrollToItem` (defaults to true, which means default implementation, see the gif bellow) ![scrolltoitem](https://user-images.githubusercontent.com/8135252/42452843-70648be2-838b-11e8-94e4-c69383454980.gif) If you are not satisfied you can pass your own function which takes (container: HTMLDivElement, Item: HTMLDivElement), [take a look for default implementation as an example](https://github.com/webscopeio/react-textarea-autocomplete/pull/76/files#diff-b5c8f479fd199c465e4e5fce91bed60dR3). Example of usage: ```js <ReactTextareaAutocomplete ... scrollToItem={false} // to disable ``` ```js <ReactTextareaAutocomplete ... scrollToItem={true} // to enable default implementation ``` ```js <ReactTextareaAutocomplete ... scrollToItem={(container, item) => {/*do stuff*/}} ```
1 parent cc0c430 commit b4a27c9

File tree

7 files changed

+70
-6
lines changed

7 files changed

+70
-6
lines changed

.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"no-underscore-dangle": 0,
1111
"no-console": 0,
1212
"flowtype/no-weak-types": 0,
13+
"import/prefer-default-export": 0,
1314
"react/forbid-prop-types": [0, { "forbid": ["object"] }]
1415
},
1516
"globals": {

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ or there is UMD build available. [Check out this pen as example](https://codepen
4141
| **trigger*** | Object: Trigger type | Define triggers and their corresponding behavior
4242
| **loadingComponent*** | React Component | Gets `data` props which is already fetched (and displayed) suggestion
4343
| innerRef | Function: (HTMLTextAreaElement) => void) | Allows you to get React ref of the underlying textarea
44+
| scrollToItem | boolean \| (container: HTMLDivElement, item: HTMLDivElement) => void) | Defaults to true. With default implementation it will scroll the dropdown every time when the item gets out of the view.
4445
| minChar | Number | Number of characters that user should type for trigger a suggestion. Defaults to 1.
4546
| onCaretPositionChange | Function: (number) => void | Listener called every time the textarea's caret position is changed. The listener is called with one attribute - caret position denoted by an integer number.
4647
| closeOnClickOutside | boolean | When it's true autocomplete will close when use click outside. Defaults to false.

example/App.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ class App extends React.Component {
308308
},
309309
}}
310310
/>
311-
{!showSecondTextarea ? null :
311+
{!showSecondTextarea ? null : (
312312
<ReactTextareaAutocomplete
313313
style={{
314314
padding: 5,
@@ -336,7 +336,7 @@ class App extends React.Component {
336336
},
337337
}}
338338
/>
339-
}
339+
)}
340340
</div>
341341
);
342342
}

src/List.jsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@ export default class List extends React.Component<ListProps, ListState> {
7979
onSelect(getTextToReplace(value));
8080
};
8181

82-
selectItem = (item: Object | string) => {
82+
selectItem = (item: Object | string, keyboard: boolean = false) => {
8383
this.setState({ selectedItem: item }, () => {
84-
this.itemsRef[this.getId(item)].scrollIntoView();
84+
if (keyboard) {
85+
this.props.dropdownScroll(this.itemsRef[this.getId(item)]);
86+
}
8587
});
8688
};
8789

@@ -107,7 +109,10 @@ export default class List extends React.Component<ListProps, ListState> {
107109
}
108110

109111
newPosition = (newPosition % values.length + values.length) % values.length; // eslint-disable-line
110-
this.selectItem(values[newPosition]);
112+
this.selectItem(
113+
values[newPosition],
114+
[KEY_CODES.DOWN, KEY_CODES.UP].includes(code)
115+
);
111116
};
112117

113118
isSelected = (item: Object | string): boolean => {

src/Textarea.jsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import getCaretCoordinates from 'textarea-caret';
77
import Listeners, { KEY_CODES } from './listener';
88
import List from './List';
99

10+
import { defaultScrollToItem } from './utilities';
11+
1012
import type {
1113
TextareaProps,
1214
TextareaState,
@@ -34,6 +36,7 @@ class ReactTextareaAutocomplete extends React.Component<
3436
movePopupAsYouType: false,
3537
value: '',
3638
minChar: 1,
39+
scrollToItem: true,
3740
};
3841

3942
constructor(props: TextareaProps) {
@@ -362,6 +365,7 @@ class ReactTextareaAutocomplete extends React.Component<
362365
'loadingComponent',
363366
'containerStyle',
364367
'minChar',
368+
'scrollToItem',
365369
'ref',
366370
'innerRef',
367371
'onChange',
@@ -546,11 +550,30 @@ class ReactTextareaAutocomplete extends React.Component<
546550
}
547551
};
548552

553+
_dropdownScroll = (item: HTMLDivElement) => {
554+
const { scrollToItem } = this.props;
555+
556+
if (!scrollToItem) return;
557+
558+
if (scrollToItem === true) {
559+
defaultScrollToItem(this.dropdownRef, item);
560+
return;
561+
}
562+
563+
if (typeof scrollToItem !== 'function' || scrollToItem.length !== 2) {
564+
throw new Error(
565+
'`scrollToItem` has to be boolean (true for default implementation) or function with two parameters: container, item.'
566+
);
567+
}
568+
569+
scrollToItem(this.dropdownRef, item);
570+
};
571+
549572
props: TextareaProps;
550573

551574
textareaRef: HTMLInputElement;
552575

553-
dropdownRef: ?HTMLDivElement;
576+
dropdownRef: HTMLDivElement;
554577

555578
tokenRegExp: RegExp;
556579

@@ -610,6 +633,7 @@ class ReactTextareaAutocomplete extends React.Component<
610633
currentTrigger && (
611634
<div
612635
ref={ref => {
636+
// $FlowFixMe
613637
this.dropdownRef = ref;
614638
}}
615639
style={{ top, left, ...dropdownStyle }}
@@ -627,6 +651,7 @@ class ReactTextareaAutocomplete extends React.Component<
627651
itemStyle={itemStyle}
628652
getTextToReplace={textToReplace}
629653
onSelect={this._onSelect}
654+
dropdownScroll={this._dropdownScroll}
630655
/>
631656
)}
632657
{dataLoading && (

src/types.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export type ListProps = {
3737
className: ?string,
3838
itemClassName: ?string,
3939
onSelect: textToReplaceType => void,
40+
dropdownScroll: HTMLDivElement => void,
4041
};
4142

4243
/**
@@ -69,6 +70,9 @@ export type TextareaProps = {
6970
onBlur: ?(SyntheticEvent<*> | Event) => void,
7071
onCaretPositionChange: ?(number) => void,
7172
innerRef: ?(HTMLTextAreaElement) => void,
73+
scrollToItem:
74+
| boolean
75+
| ((container: HTMLDivElement, item: HTMLDivElement) => void),
7276
closeOnClickOutside?: boolean,
7377
movePopupAsYouType?: boolean,
7478
minChar: ?number,

src/utilities.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// @flow
2+
3+
export function defaultScrollToItem(
4+
container: HTMLDivElement,
5+
item: HTMLDivElement
6+
) {
7+
const itemHeight = parseInt(
8+
getComputedStyle(item).getPropertyValue('height'),
9+
10
10+
);
11+
12+
const containerHight =
13+
parseInt(getComputedStyle(container).getPropertyValue('height'), 10) -
14+
itemHeight;
15+
16+
const itemOffsetTop = item.offsetTop;
17+
const actualScrollTop = container.scrollTop;
18+
19+
if (
20+
itemOffsetTop < actualScrollTop + containerHight &&
21+
actualScrollTop < itemOffsetTop
22+
) {
23+
return;
24+
}
25+
26+
// eslint-disable-next-line
27+
container.scrollTop = itemOffsetTop;
28+
}

0 commit comments

Comments
 (0)