-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[core] fix(TextArea): resize vertically in controlled mode #5975
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,7 +50,7 @@ export interface ITextAreaProps extends IntentProps, Props, React.TextareaHTMLAt | |
inputRef?: React.Ref<HTMLTextAreaElement>; | ||
} | ||
|
||
export interface ITextAreaState { | ||
export interface TextAreaState { | ||
height?: number; | ||
} | ||
|
||
|
@@ -61,10 +61,10 @@ export interface ITextAreaState { | |
* | ||
* @see https://blueprintjs.com/docs/#core/components/text-inputs.text-area | ||
*/ | ||
export class TextArea extends AbstractPureComponent2<TextAreaProps, ITextAreaState> { | ||
export class TextArea extends AbstractPureComponent2<TextAreaProps, TextAreaState> { | ||
public static displayName = `${DISPLAYNAME_PREFIX}.TextArea`; | ||
|
||
public state: ITextAreaState = {}; | ||
public state: TextAreaState = {}; | ||
|
||
// used to measure and set the height of the component on first mount | ||
public textareaElement: HTMLTextAreaElement | null = null; | ||
|
@@ -75,14 +75,17 @@ export class TextArea extends AbstractPureComponent2<TextAreaProps, ITextAreaSta | |
this.props.inputRef, | ||
); | ||
|
||
public componentDidMount() { | ||
if (this.props.growVertically && this.textareaElement !== null) { | ||
// HACKHACK: this should probably be done in getSnapshotBeforeUpdate | ||
/* eslint-disable-next-line react/no-did-mount-set-state */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see a significantly better way to do this, and I think the potential for layout thrashing is minimal. removed this comment. |
||
this.setState({ | ||
height: this.textareaElement?.scrollHeight, | ||
}); | ||
private maybeSyncHeightToScrollHeight = () => { | ||
if (this.props.growVertically && this.textareaElement != null) { | ||
const { scrollHeight } = this.textareaElement; | ||
if (scrollHeight > 0) { | ||
this.setState({ height: scrollHeight }); | ||
} | ||
} | ||
}; | ||
|
||
public componentDidMount() { | ||
this.maybeSyncHeightToScrollHeight(); | ||
} | ||
|
||
public componentDidUpdate(prevProps: TextAreaProps) { | ||
|
@@ -91,6 +94,10 @@ export class TextArea extends AbstractPureComponent2<TextAreaProps, ITextAreaSta | |
this.handleRef = refHandler(this, "textareaElement", this.props.inputRef); | ||
setRef(this.props.inputRef, this.textareaElement); | ||
} | ||
|
||
if (prevProps.value !== this.props.value || prevProps.style !== this.props.style) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note that |
||
this.maybeSyncHeightToScrollHeight(); | ||
} | ||
} | ||
|
||
public render() { | ||
|
@@ -130,14 +137,7 @@ export class TextArea extends AbstractPureComponent2<TextAreaProps, ITextAreaSta | |
} | ||
|
||
private handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
if (this.props.growVertically) { | ||
this.setState({ | ||
height: e.target.scrollHeight, | ||
}); | ||
} | ||
|
||
if (this.props.onChange != null) { | ||
this.props.onChange(e); | ||
} | ||
this.maybeSyncHeightToScrollHeight(); | ||
this.props.onChange?.(e); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
* Copyright 2023 Palantir Technologies, Inc. All rights reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import * as React from "react"; | ||
|
||
import { AnchorButton, ControlGroup, H5, Switch, TextArea } from "@blueprintjs/core"; | ||
import { Example, ExampleProps, handleBooleanChange } from "@blueprintjs/docs-theme"; | ||
import { Tooltip2 } from "@blueprintjs/popover2"; | ||
|
||
const INTITIAL_CONTROLLED_TEXT = "In a galaxy far, far away..."; | ||
const CONTROLLED_TEXT_TO_APPEND = | ||
"The approach will not be easy. You are required to maneuver straight down this trench and skim the surface to this point. The target area is only two meters wide. It's a small thermal exhaust port, right below the main port. The shaft leads directly to the reactor system."; | ||
|
||
interface TextAreaExampleState { | ||
controlled: boolean; | ||
disabled: boolean; | ||
growVertically: boolean; | ||
large: boolean; | ||
readOnly: boolean; | ||
small: boolean; | ||
value: string; | ||
} | ||
|
||
export class TextAreaExample extends React.PureComponent<ExampleProps, TextAreaExampleState> { | ||
public state: TextAreaExampleState = { | ||
controlled: false, | ||
disabled: false, | ||
growVertically: true, | ||
large: false, | ||
readOnly: false, | ||
small: false, | ||
value: INTITIAL_CONTROLLED_TEXT, | ||
}; | ||
|
||
private handleControlledChange = handleBooleanChange(controlled => this.setState({ controlled })); | ||
|
||
private handleDisabledChange = handleBooleanChange(disabled => this.setState({ disabled })); | ||
|
||
private handleGrowVerticallyChange = handleBooleanChange(growVertically => this.setState({ growVertically })); | ||
|
||
private handleLargeChange = handleBooleanChange(large => this.setState({ large, ...(large && { small: false }) })); | ||
|
||
private handleReadOnlyChange = handleBooleanChange(readOnly => this.setState({ readOnly })); | ||
|
||
private handleSmallChange = handleBooleanChange(small => this.setState({ small, ...(small && { large: false }) })); | ||
|
||
private appendControlledText = () => | ||
this.setState(({ value }) => ({ value: value + " " + CONTROLLED_TEXT_TO_APPEND })); | ||
|
||
private resetControlledText = () => this.setState({ value: INTITIAL_CONTROLLED_TEXT }); | ||
|
||
public render() { | ||
const { controlled, value, ...textAreaProps } = this.state; | ||
|
||
return ( | ||
<Example options={this.renderOptions()} {...this.props}> | ||
<TextArea style={{ display: controlled ? undefined : "none" }} value={value} {...textAreaProps} /> | ||
<TextArea | ||
style={{ display: controlled ? "none" : undefined }} | ||
placeholder="Type something..." | ||
{...textAreaProps} | ||
/> | ||
</Example> | ||
); | ||
} | ||
|
||
private renderOptions() { | ||
const { controlled, disabled, growVertically, large, readOnly, small } = this.state; | ||
return ( | ||
<> | ||
<H5>Appearance props</H5> | ||
<Switch label="Large" disabled={small} onChange={this.handleLargeChange} checked={large} /> | ||
<Switch label="Small" disabled={large} onChange={this.handleSmallChange} checked={small} /> | ||
<H5>Behavior props</H5> | ||
<Switch label="Disabled" onChange={this.handleDisabledChange} checked={disabled} /> | ||
<Switch label="Read-only" onChange={this.handleReadOnlyChange} checked={readOnly} /> | ||
<Switch label="Grow vertically" onChange={this.handleGrowVerticallyChange} checked={growVertically} /> | ||
<Switch label="Controlled usage" onChange={this.handleControlledChange} checked={controlled} /> | ||
<ControlGroup> | ||
<AnchorButton | ||
disabled={!controlled} | ||
text="Insert more text" | ||
icon="plus" | ||
onClick={this.appendControlledText} | ||
/> | ||
<Tooltip2 content="Reset text" placement="bottom-end"> | ||
<AnchorButton disabled={!controlled} icon="reset" onClick={this.resetControlledText} /> | ||
</Tooltip2> | ||
</ControlGroup> | ||
</> | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't really help us. removed this comment