mirror of
https://github.com/element-hq/element-web
synced 2024-11-23 09:46:09 +03:00
Merge pull request #4635 from JorikSchellekens/joriks/field-ts
Move Field to Typescript
This commit is contained in:
commit
076a3e058d
11 changed files with 632 additions and 535 deletions
|
@ -119,10 +119,12 @@
|
||||||
"@peculiar/webcrypto": "^1.0.22",
|
"@peculiar/webcrypto": "^1.0.22",
|
||||||
"@types/classnames": "^2.2.10",
|
"@types/classnames": "^2.2.10",
|
||||||
"@types/flux": "^3.1.9",
|
"@types/flux": "^3.1.9",
|
||||||
|
"@types/lodash": "^4.14.152",
|
||||||
"@types/modernizr": "^3.5.3",
|
"@types/modernizr": "^3.5.3",
|
||||||
"@types/node": "^12.12.41",
|
"@types/node": "^12.12.41",
|
||||||
"@types/qrcode": "^1.3.4",
|
"@types/qrcode": "^1.3.4",
|
||||||
"@types/react": "16.9",
|
"@types/react": "^16.9",
|
||||||
|
"@types/react-dom": "^16.9.8",
|
||||||
"@types/zxcvbn": "^4.4.0",
|
"@types/zxcvbn": "^4.4.0",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"babel-jest": "^24.9.0",
|
"babel-jest": "^24.9.0",
|
||||||
|
|
|
@ -69,7 +69,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(EMOJI_REGEX);
|
super(EMOJI_REGEX);
|
||||||
this.matcher = new QueryMatcher(EMOJI_SHORTNAMES, {
|
this.matcher = new QueryMatcher<IEmojiShort>(EMOJI_SHORTNAMES, {
|
||||||
keys: ['emoji.emoticon', 'shortname'],
|
keys: ['emoji.emoticon', 'shortname'],
|
||||||
funcs: [
|
funcs: [
|
||||||
(o) => o.emoji.shortcodes.length > 1 ? o.emoji.shortcodes.slice(1).map(s => `:${s}:`).join(" ") : "", // aliases
|
(o) => o.emoji.shortcodes.length > 1 ? o.emoji.shortcodes.slice(1).map(s => `:${s}:`).join(" ") : "", // aliases
|
||||||
|
|
|
@ -45,7 +45,7 @@ interface IOptions<T extends {}> {
|
||||||
* @param {function[]} options.funcs List of functions that when called with the
|
* @param {function[]} options.funcs List of functions that when called with the
|
||||||
* object as an arg will return a string to use as an index
|
* object as an arg will return a string to use as an index
|
||||||
*/
|
*/
|
||||||
export default class QueryMatcher<T> {
|
export default class QueryMatcher<T extends Object> {
|
||||||
private _options: IOptions<T>;
|
private _options: IOptions<T>;
|
||||||
private _keys: IOptions<T>["keys"];
|
private _keys: IOptions<T>["keys"];
|
||||||
private _funcs: Required<IOptions<T>["funcs"]>;
|
private _funcs: Required<IOptions<T>["funcs"]>;
|
||||||
|
@ -75,7 +75,11 @@ export default class QueryMatcher<T> {
|
||||||
this._items = new Map();
|
this._items = new Map();
|
||||||
|
|
||||||
for (const object of objects) {
|
for (const object of objects) {
|
||||||
const keyValues = _at(object, this._keys);
|
// Need to use unsafe coerce here because the objects can have any
|
||||||
|
// type for their values. We assume that those values who's keys have
|
||||||
|
// been specified will be string. Also, we cannot infer all the
|
||||||
|
// types of the keys of the objects at compile.
|
||||||
|
const keyValues = _at<string>(<any>object, this._keys);
|
||||||
|
|
||||||
for (const f of this._funcs) {
|
for (const f of this._funcs) {
|
||||||
keyValues.push(f(object));
|
keyValues.push(f(object));
|
||||||
|
|
|
@ -36,7 +36,7 @@ interface IProps {
|
||||||
labelStrongPassword?: string;
|
labelStrongPassword?: string;
|
||||||
labelAllowedButUnsafe?: string;
|
labelAllowedButUnsafe?: string;
|
||||||
|
|
||||||
onChange(ev: KeyboardEvent);
|
onChange(ev: React.FormEvent<HTMLElement>);
|
||||||
onValidate(result: IValidationResult);
|
onValidate(result: IValidationResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,10 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
import {IFieldState, IValidationResult} from "../elements/Validation";
|
||||||
|
|
||||||
// Invoke validation from user input (when typing, etc.) at most once every N ms.
|
// Invoke validation from user input (when typing, etc.) at most once every N ms.
|
||||||
const VALIDATION_THROTTLE_MS = 200;
|
const VALIDATION_THROTTLE_MS = 200;
|
||||||
|
@ -29,58 +29,93 @@ function getId() {
|
||||||
return `${BASE_ID}_${count++}`;
|
return `${BASE_ID}_${count++}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Field extends React.PureComponent {
|
interface IProps extends React.InputHTMLAttributes<HTMLSelectElement | HTMLInputElement> {
|
||||||
static propTypes = {
|
|
||||||
// The field's ID, which binds the input and label together. Immutable.
|
// The field's ID, which binds the input and label together. Immutable.
|
||||||
id: PropTypes.string,
|
id?: string,
|
||||||
// The element to create. Defaults to "input".
|
// The element to create. Defaults to "input".
|
||||||
// To define options for a select, use <Field><option ... /></Field>
|
// To define options for a select, use <Field><option ... /></Field>
|
||||||
element: PropTypes.oneOf(["input", "select", "textarea"]),
|
element?: "input" | " select" | "textarea",
|
||||||
// The field's type (when used as an <input>). Defaults to "text".
|
// The field's type (when used as an <input>). Defaults to "text".
|
||||||
type: PropTypes.string,
|
type?: string,
|
||||||
// id of a <datalist> element for suggestions
|
// id of a <datalist> element for suggestions
|
||||||
list: PropTypes.string,
|
list?: string,
|
||||||
// The field's label string.
|
// The field's label string.
|
||||||
label: PropTypes.string,
|
label?: string,
|
||||||
// The field's placeholder string. Defaults to the label.
|
// The field's placeholder string. Defaults to the label.
|
||||||
placeholder: PropTypes.string,
|
placeholder?: string,
|
||||||
// The field's value.
|
// The field's value.
|
||||||
// This is a controlled component, so the value is required.
|
// This is a controlled component, so the value is required.
|
||||||
value: PropTypes.string.isRequired,
|
value: string,
|
||||||
// Optional component to include inside the field before the input.
|
// Optional component to include inside the field before the input.
|
||||||
prefix: PropTypes.node,
|
prefixComponent?: React.ReactNode,
|
||||||
// Optional component to include inside the field after the input.
|
// Optional component to include inside the field after the input.
|
||||||
postfix: PropTypes.node,
|
postfixComponent?: React.ReactNode,
|
||||||
// The callback called whenever the contents of the field
|
// The callback called whenever the contents of the field
|
||||||
// changes. Returns an object with `valid` boolean field
|
// changes. Returns an object with `valid` boolean field
|
||||||
// and a `feedback` react component field to provide feedback
|
// and a `feedback` react component field to provide feedback
|
||||||
// to the user.
|
// to the user.
|
||||||
onValidate: PropTypes.func,
|
onValidate?: (input: IFieldState) => Promise<IValidationResult>,
|
||||||
// If specified, overrides the value returned by onValidate.
|
// If specified, overrides the value returned by onValidate.
|
||||||
flagInvalid: PropTypes.bool,
|
flagInvalid?: boolean,
|
||||||
// If specified, contents will appear as a tooltip on the element and
|
// If specified, contents will appear as a tooltip on the element and
|
||||||
// validation feedback tooltips will be suppressed.
|
// validation feedback tooltips will be suppressed.
|
||||||
tooltipContent: PropTypes.node,
|
tooltipContent?: React.ReactNode,
|
||||||
// If specified alongside tooltipContent, the class name to apply to the
|
// If specified alongside tooltipContent, the class name to apply to the
|
||||||
// tooltip itself.
|
// tooltip itself.
|
||||||
tooltipClassName: PropTypes.string,
|
tooltipClassName?: string,
|
||||||
// If specified, an additional class name to apply to the field container
|
// If specified, an additional class name to apply to the field container
|
||||||
className: PropTypes.string,
|
className?: string,
|
||||||
// All other props pass through to the <input>.
|
// All other props pass through to the <input>.
|
||||||
};
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
valid: boolean,
|
||||||
|
feedback: React.ReactNode,
|
||||||
|
feedbackVisible: boolean,
|
||||||
|
focused: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Field extends React.PureComponent<IProps, IState> {
|
||||||
|
private id: string;
|
||||||
|
private input: HTMLInputElement;
|
||||||
|
|
||||||
|
private static defaultProps = {
|
||||||
|
element: "input",
|
||||||
|
type: "text",
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This was changed from throttle to debounce: this is more traditional for
|
||||||
|
* form validation since it means that the validation doesn't happen at all
|
||||||
|
* until the user stops typing for a bit (debounce defaults to not running on
|
||||||
|
* the leading edge). If we're doing an HTTP hit on each validation, we have more
|
||||||
|
* incentive to prevent validating input that's very unlikely to be valid.
|
||||||
|
* We may find that we actually want different behaviour for registration
|
||||||
|
* fields, in which case we can add some options to control it.
|
||||||
|
*/
|
||||||
|
private validateOnChange = debounce(() => {
|
||||||
|
this.validate({
|
||||||
|
focused: true,
|
||||||
|
});
|
||||||
|
}, VALIDATION_THROTTLE_MS);
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
valid: undefined,
|
valid: undefined,
|
||||||
feedback: undefined,
|
feedback: undefined,
|
||||||
|
feedbackVisible: false,
|
||||||
focused: false,
|
focused: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.id = this.props.id || getId();
|
this.id = this.props.id || getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFocus = (ev) => {
|
public focus() {
|
||||||
|
this.input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onFocus = (ev) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
focused: true,
|
focused: true,
|
||||||
});
|
});
|
||||||
|
@ -93,7 +128,7 @@ export default class Field extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onChange = (ev) => {
|
private onChange = (ev) => {
|
||||||
this.validateOnChange();
|
this.validateOnChange();
|
||||||
// Parent component may have supplied its own `onChange` as well
|
// Parent component may have supplied its own `onChange` as well
|
||||||
if (this.props.onChange) {
|
if (this.props.onChange) {
|
||||||
|
@ -101,7 +136,7 @@ export default class Field extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onBlur = (ev) => {
|
private onBlur = (ev) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
focused: false,
|
focused: false,
|
||||||
});
|
});
|
||||||
|
@ -114,11 +149,7 @@ export default class Field extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
focus() {
|
private async validate({ focused, allowEmpty = true }: {focused: boolean, allowEmpty?: boolean}) {
|
||||||
this.input.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
async validate({ focused, allowEmpty = true }) {
|
|
||||||
if (!this.props.onValidate) {
|
if (!this.props.onValidate) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -149,56 +180,42 @@ export default class Field extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* This was changed from throttle to debounce: this is more traditional for
|
|
||||||
* form validation since it means that the validation doesn't happen at all
|
|
||||||
* until the user stops typing for a bit (debounce defaults to not running on
|
|
||||||
* the leading edge). If we're doing an HTTP hit on each validation, we have more
|
|
||||||
* incentive to prevent validating input that's very unlikely to be valid.
|
|
||||||
* We may find that we actually want different behaviour for registration
|
|
||||||
* fields, in which case we can add some options to control it.
|
|
||||||
*/
|
|
||||||
validateOnChange = debounce(() => {
|
|
||||||
this.validate({
|
|
||||||
focused: true,
|
|
||||||
});
|
|
||||||
}, VALIDATION_THROTTLE_MS);
|
|
||||||
|
|
||||||
render() {
|
|
||||||
|
public render() {
|
||||||
const {
|
const {
|
||||||
element, prefix, postfix, className, onValidate, children,
|
element, prefixComponent, postfixComponent, className, onValidate, children,
|
||||||
tooltipContent, flagInvalid, tooltipClassName, list, ...inputProps} = this.props;
|
tooltipContent, flagInvalid, tooltipClassName, list, ...inputProps} = this.props;
|
||||||
|
|
||||||
const inputElement = element || "input";
|
|
||||||
|
|
||||||
// Set some defaults for the <input> element
|
// Set some defaults for the <input> element
|
||||||
inputProps.type = inputProps.type || "text";
|
const ref = input => this.input = input;
|
||||||
inputProps.ref = input => this.input = input;
|
|
||||||
inputProps.placeholder = inputProps.placeholder || inputProps.label;
|
inputProps.placeholder = inputProps.placeholder || inputProps.label;
|
||||||
inputProps.id = this.id; // this overwrites the id from props
|
inputProps.id = this.id; // this overwrites the id from props
|
||||||
|
|
||||||
inputProps.onFocus = this.onFocus;
|
inputProps.onFocus = this.onFocus;
|
||||||
inputProps.onChange = this.onChange;
|
inputProps.onChange = this.onChange;
|
||||||
inputProps.onBlur = this.onBlur;
|
inputProps.onBlur = this.onBlur;
|
||||||
inputProps.list = list;
|
|
||||||
|
|
||||||
const fieldInput = React.createElement(inputElement, inputProps, children);
|
// Appease typescript's inference
|
||||||
|
const inputProps_ = {...inputProps, ref, list};
|
||||||
|
|
||||||
|
const fieldInput = React.createElement(this.props.element, inputProps_, children);
|
||||||
|
|
||||||
let prefixContainer = null;
|
let prefixContainer = null;
|
||||||
if (prefix) {
|
if (prefixComponent) {
|
||||||
prefixContainer = <span className="mx_Field_prefix">{prefix}</span>;
|
prefixContainer = <span className="mx_Field_prefix">{prefixComponent}</span>;
|
||||||
}
|
}
|
||||||
let postfixContainer = null;
|
let postfixContainer = null;
|
||||||
if (postfix) {
|
if (postfixComponent) {
|
||||||
postfixContainer = <span className="mx_Field_postfix">{postfix}</span>;
|
postfixContainer = <span className="mx_Field_postfix">{postfixComponent}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasValidationFlag = flagInvalid !== null && flagInvalid !== undefined;
|
const hasValidationFlag = flagInvalid !== null && flagInvalid !== undefined;
|
||||||
const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, className, {
|
const fieldClasses = classNames("mx_Field", `mx_Field_${this.props.element}`, className, {
|
||||||
// If we have a prefix element, leave the label always at the top left and
|
// If we have a prefix element, leave the label always at the top left and
|
||||||
// don't animate it, as it looks a bit clunky and would add complexity to do
|
// don't animate it, as it looks a bit clunky and would add complexity to do
|
||||||
// properly.
|
// properly.
|
||||||
mx_Field_labelAlwaysTopLeft: prefix,
|
mx_Field_labelAlwaysTopLeft: prefixComponent,
|
||||||
mx_Field_valid: onValidate && this.state.valid === true,
|
mx_Field_valid: onValidate && this.state.valid === true,
|
||||||
mx_Field_invalid: hasValidationFlag
|
mx_Field_invalid: hasValidationFlag
|
||||||
? flagInvalid
|
? flagInvalid
|
|
@ -2,7 +2,7 @@
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2019 New Vector Ltd
|
Copyright 2019 New Vector Ltd
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -18,67 +18,68 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
import React, { Component } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import createReactClass from 'create-react-class';
|
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { ViewTooltipPayload } from '../../../dispatcher/payloads/ViewTooltipPayload';
|
||||||
|
import { Action } from '../../../dispatcher/actions';
|
||||||
|
|
||||||
const MIN_TOOLTIP_HEIGHT = 25;
|
const MIN_TOOLTIP_HEIGHT = 25;
|
||||||
|
|
||||||
export default createReactClass({
|
interface IProps {
|
||||||
displayName: 'Tooltip',
|
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
// Class applied to the element used to position the tooltip
|
// Class applied to the element used to position the tooltip
|
||||||
className: PropTypes.string,
|
className: string,
|
||||||
// Class applied to the tooltip itself
|
// Class applied to the tooltip itself
|
||||||
tooltipClassName: PropTypes.string,
|
tooltipClassName?: string,
|
||||||
// Whether the tooltip is visible or hidden.
|
// Whether the tooltip is visible or hidden.
|
||||||
// The hidden state allows animating the tooltip away via CSS.
|
// The hidden state allows animating the tooltip away via CSS.
|
||||||
// Defaults to visible if unset.
|
// Defaults to visible if unset.
|
||||||
visible: PropTypes.bool,
|
visible?: boolean,
|
||||||
// the react element to put into the tooltip
|
// the react element to put into the tooltip
|
||||||
label: PropTypes.node,
|
label: React.ReactNode,
|
||||||
},
|
}
|
||||||
|
|
||||||
getDefaultProps() {
|
export default class Tooltip extends React.Component<IProps> {
|
||||||
return {
|
private tooltipContainer: HTMLElement;
|
||||||
|
private tooltip: void | Element | Component<Element, any, any>;
|
||||||
|
private parent: Element;
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly defaultProps = {
|
||||||
visible: true,
|
visible: true,
|
||||||
};
|
};
|
||||||
},
|
|
||||||
|
|
||||||
// Create a wrapper for the tooltip outside the parent and attach it to the body element
|
// Create a wrapper for the tooltip outside the parent and attach it to the body element
|
||||||
componentDidMount: function() {
|
public componentDidMount() {
|
||||||
this.tooltipContainer = document.createElement("div");
|
this.tooltipContainer = document.createElement("div");
|
||||||
this.tooltipContainer.className = "mx_Tooltip_wrapper";
|
this.tooltipContainer.className = "mx_Tooltip_wrapper";
|
||||||
document.body.appendChild(this.tooltipContainer);
|
document.body.appendChild(this.tooltipContainer);
|
||||||
window.addEventListener('scroll', this._renderTooltip, true);
|
window.addEventListener('scroll', this.renderTooltip, true);
|
||||||
|
|
||||||
this.parent = ReactDOM.findDOMNode(this).parentNode;
|
this.parent = ReactDOM.findDOMNode(this).parentNode as Element;
|
||||||
|
|
||||||
this._renderTooltip();
|
this.renderTooltip();
|
||||||
},
|
}
|
||||||
|
|
||||||
componentDidUpdate: function() {
|
public componentDidUpdate() {
|
||||||
this._renderTooltip();
|
this.renderTooltip();
|
||||||
},
|
}
|
||||||
|
|
||||||
// Remove the wrapper element, as the tooltip has finished using it
|
// Remove the wrapper element, as the tooltip has finished using it
|
||||||
componentWillUnmount: function() {
|
public componentWillUnmount() {
|
||||||
dis.dispatch({
|
dis.dispatch<ViewTooltipPayload>({
|
||||||
action: 'view_tooltip',
|
action: Action.ViewTooltip,
|
||||||
tooltip: null,
|
tooltip: null,
|
||||||
parent: null,
|
parent: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
ReactDOM.unmountComponentAtNode(this.tooltipContainer);
|
ReactDOM.unmountComponentAtNode(this.tooltipContainer);
|
||||||
document.body.removeChild(this.tooltipContainer);
|
document.body.removeChild(this.tooltipContainer);
|
||||||
window.removeEventListener('scroll', this._renderTooltip, true);
|
window.removeEventListener('scroll', this.renderTooltip, true);
|
||||||
},
|
}
|
||||||
|
|
||||||
_updatePosition(style) {
|
private updatePosition(style: {[key: string]: any}) {
|
||||||
const parentBox = this.parent.getBoundingClientRect();
|
const parentBox = this.parent.getBoundingClientRect();
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
if (parentBox.height > MIN_TOOLTIP_HEIGHT) {
|
if (parentBox.height > MIN_TOOLTIP_HEIGHT) {
|
||||||
|
@ -91,16 +92,15 @@ export default createReactClass({
|
||||||
style.top = (parentBox.top - 2) + window.pageYOffset + offset;
|
style.top = (parentBox.top - 2) + window.pageYOffset + offset;
|
||||||
style.left = 6 + parentBox.right + window.pageXOffset;
|
style.left = 6 + parentBox.right + window.pageXOffset;
|
||||||
return style;
|
return style;
|
||||||
},
|
}
|
||||||
|
|
||||||
_renderTooltip: function() {
|
private renderTooltip() {
|
||||||
// Add the parent's position to the tooltips, so it's correctly
|
// Add the parent's position to the tooltips, so it's correctly
|
||||||
// positioned, also taking into account any window zoom
|
// positioned, also taking into account any window zoom
|
||||||
// NOTE: The additional 6 pixels for the left position, is to take account of the
|
// NOTE: The additional 6 pixels for the left position, is to take account of the
|
||||||
// tooltips chevron
|
// tooltips chevron
|
||||||
const parent = ReactDOM.findDOMNode(this).parentNode;
|
const parent = ReactDOM.findDOMNode(this).parentNode as Element;
|
||||||
let style = {};
|
const style = this.updatePosition({});
|
||||||
style = this._updatePosition(style);
|
|
||||||
// Hide the entire container when not visible. This prevents flashing of the tooltip
|
// Hide the entire container when not visible. This prevents flashing of the tooltip
|
||||||
// if it is not meant to be visible on first mount.
|
// if it is not meant to be visible on first mount.
|
||||||
style.display = this.props.visible ? "block" : "none";
|
style.display = this.props.visible ? "block" : "none";
|
||||||
|
@ -118,21 +118,21 @@ export default createReactClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
// Render the tooltip manually, as we wish it not to be rendered within the parent
|
// Render the tooltip manually, as we wish it not to be rendered within the parent
|
||||||
this.tooltip = ReactDOM.render(tooltip, this.tooltipContainer);
|
this.tooltip = ReactDOM.render<Element>(tooltip, this.tooltipContainer);
|
||||||
|
|
||||||
// Tell the roomlist about us so it can manipulate us if it wishes
|
// Tell the roomlist about us so it can manipulate us if it wishes
|
||||||
dis.dispatch({
|
dis.dispatch<ViewTooltipPayload>({
|
||||||
action: 'view_tooltip',
|
action: Action.ViewTooltip,
|
||||||
tooltip: this.tooltip,
|
tooltip: this.tooltip,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
render: function() {
|
public render() {
|
||||||
// Render a placeholder
|
// Render a placeholder
|
||||||
return (
|
return (
|
||||||
<div className={this.props.className} >
|
<div className={this.props.className} >
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
|
@ -214,7 +214,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
let tooltip = null;
|
let tooltip = null;
|
||||||
if (false) { // isCollapsed
|
if (false) { // isCollapsed
|
||||||
if (this.state.hover) {
|
if (this.state.hover) {
|
||||||
tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto"/>
|
tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -267,7 +267,7 @@ export default class PhoneNumbers extends React.Component {
|
||||||
label={_t("Phone Number")}
|
label={_t("Phone Number")}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
disabled={this.state.verifying}
|
disabled={this.state.verifying}
|
||||||
prefix={phoneCountry}
|
prefixComponent={phoneCountry}
|
||||||
value={this.state.newPhoneNumber}
|
value={this.state.newPhoneNumber}
|
||||||
onChange={this._onChangeNewPhoneNumber}
|
onChange={this._onChangeNewPhoneNumber}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -38,5 +38,10 @@ export enum Action {
|
||||||
* Open the user settings. No additional payload information required.
|
* Open the user settings. No additional payload information required.
|
||||||
*/
|
*/
|
||||||
ViewUserSettings = "view_user_settings",
|
ViewUserSettings = "view_user_settings",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current tooltip. Should be use with ViewTooltipPayload.
|
||||||
|
*/
|
||||||
|
ViewTooltip = "view_tooltip",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
35
src/dispatcher/payloads/ViewTooltipPayload.ts
Normal file
35
src/dispatcher/payloads/ViewTooltipPayload.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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 { ActionPayload } from "../payloads";
|
||||||
|
import { Action } from "../actions";
|
||||||
|
import { Component } from "react";
|
||||||
|
|
||||||
|
export interface ViewTooltipPayload extends ActionPayload {
|
||||||
|
action: Action.ViewTooltip,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The tooltip to render. If it's null the tooltip will not be rendered
|
||||||
|
* We need the void type because of typescript headaches.
|
||||||
|
*/
|
||||||
|
tooltip: null | void | Element | Component<Element, any, any>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The parent under which to render the tooltip. Can be null to remove
|
||||||
|
* the parent type.
|
||||||
|
*/
|
||||||
|
parent: null | Element
|
||||||
|
}
|
Loading…
Reference in a new issue