Skip to content

Commit a0d09ad

Browse files
authored
Merge pull request nubasedev#300 from mmomtchev/suggestions-autoplace
Suggestions autoplace
2 parents 6dbd9b9 + 3eb0538 commit a0d09ad

4 files changed

Lines changed: 35 additions & 8 deletions

File tree

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ given `text` and `triggeredBy` (character that triggered the suggestions). The r
108108
The `preview` is what is going to be displayed in the suggestions box. The `value` is what is going to be inserted in the `textarea` on click or enter.
109109
- **suggestionTriggerCharacters (string[])**: Characters that will trigger mention suggestions to be loaded. This property is useless
110110
without `loadSuggestions`.
111+
- **suggestionsAutoplace?: boolean**: Try to move the suggestions popover around so that it fits in the viewport, defaults to false
111112
- **childProps?: [Object](https://github.com/andrerpena/react-mde/blob/master/src/child-props.ts#L16)**: An object containing props to be passed to `writeButton`, `previewButton`, `commandButtons` and `textArea`.
112113

113114
## Styling

src/components/ReactMde.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface ReactMdeProps {
4343
readOnly?: boolean;
4444
disablePreview?: boolean;
4545
suggestionTriggerCharacters?: string[];
46+
suggestionsAutoplace?: boolean;
4647
loadSuggestions?: (
4748
text: string,
4849
triggeredBy: string
@@ -91,7 +92,8 @@ export class ReactMde extends React.Component<ReactMdeProps, ReactMdeState> {
9192
heightUnits: "px",
9293
selectedTab: "write",
9394
disablePreview: false,
94-
suggestionTriggerCharacters: ["@"]
95+
suggestionTriggerCharacters: ["@"],
96+
suggestionsAutoplace: false
9597
};
9698

9799
constructor(props: ReactMdeProps) {
@@ -219,6 +221,7 @@ export class ReactMde extends React.Component<ReactMdeProps, ReactMdeState> {
219221
<TextArea
220222
classes={classes?.textArea}
221223
suggestionsDropdownClasses={classes?.suggestionsDropdown}
224+
suggestionsAutoplace={this.props.suggestionsAutoplace}
222225
refObject={this.finalRefs.textarea}
223226
onChange={this.handleTextChange}
224227
onPaste={this.handlePaste}

src/components/SuggestionsDropdown.tsx

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export interface SuggestionsDropdownProps {
88
classes?: ClassValue;
99
caret: CaretCoordinates;
1010
suggestions: Suggestion[];
11+
suggestionsAutoplace: boolean;
1112
onSuggestionSelected: (index: number) => void;
1213
/**
1314
* Which item is focused by the keyboard
@@ -21,6 +22,7 @@ export const SuggestionsDropdown: React.FunctionComponent<SuggestionsDropdownPro
2122
suggestions,
2223
caret,
2324
onSuggestionSelected,
25+
suggestionsAutoplace,
2426
focusIndex,
2527
textAreaRef
2628
}) => {
@@ -30,17 +32,36 @@ export const SuggestionsDropdown: React.FunctionComponent<SuggestionsDropdownPro
3032
onSuggestionSelected(index);
3133
};
3234

33-
// onMouseDown should be cancelled because onClick will handle it propertly. This way, the textarea does not lose
34-
// focus
35-
const handleMouseDown = (event: React.MouseEvent) => event.preventDefault();
35+
const handleMouseDown =
36+
(event: React.MouseEvent) => event.preventDefault();
3637

38+
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
39+
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
40+
41+
const left = caret.left - textAreaRef.current.scrollLeft;
42+
const top = caret.top - textAreaRef.current.scrollTop;
43+
44+
const style = {} as React.CSSProperties;
45+
if (suggestionsAutoplace && top +
46+
textAreaRef.current.getBoundingClientRect().top +
47+
textAreaRef.current.ownerDocument.defaultView.pageYOffset +
48+
caret.lineHeight * 1.5 * suggestions.length > vh)
49+
style.bottom = textAreaRef.current.offsetHeight - caret.top;
50+
else
51+
style.top = top;
52+
53+
if (suggestionsAutoplace && left +
54+
textAreaRef.current.getBoundingClientRect().left +
55+
textAreaRef.current.ownerDocument.defaultView.pageXOffset +
56+
caret.lineHeight * 0.6666 * Math.max.apply(Math, suggestions.map(x => x.preview.toString().length)) > vw)
57+
style.right = textAreaRef.current.offsetWidth - caret.left;
58+
else
59+
style.left = left;
60+
3761
return (
3862
<ul
3963
className={classNames("mde-suggestions", classes)}
40-
style={{
41-
left: caret.left - textAreaRef.current.scrollLeft,
42-
top: caret.top - textAreaRef.current.scrollTop
43-
}}
64+
style={style}
4465
>
4566
{suggestions.map((s, i) => (
4667
<li

src/components/TextArea.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export interface TextAreaProps {
4444
height?: number;
4545
heightUnits?: string;
4646
suggestionTriggerCharacters?: string[];
47+
suggestionsAutoplace?: boolean;
4748
loadSuggestions?: (
4849
text: string,
4950
triggeredBy: string
@@ -445,6 +446,7 @@ export class TextArea extends React.Component<TextAreaProps, TextAreaState> {
445446
caret={mention.caret}
446447
suggestions={mention.suggestions}
447448
onSuggestionSelected={this.handleSuggestionSelected}
449+
suggestionsAutoplace={this.props.suggestionsAutoplace}
448450
focusIndex={mention.focusIndex}
449451
textAreaRef={this.props.refObject}
450452
/>

0 commit comments

Comments
 (0)