Only jump to date after pressing the 'go' button (#8548)
This commit is contained in:
parent
c67b41fbde
commit
97c99c6aae
4 changed files with 4 additions and 150 deletions
|
@ -19,7 +19,6 @@ import classNames from 'classnames';
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
|
|
||||||
import { IFieldState, IValidationResult } from "./Validation";
|
import { IFieldState, IValidationResult } from "./Validation";
|
||||||
import { ComponentClass } from "../../../@types/common";
|
|
||||||
import Tooltip from "./Tooltip";
|
import Tooltip from "./Tooltip";
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -83,7 +82,6 @@ export interface IInputProps extends IProps, InputHTMLAttributes<HTMLInputElemen
|
||||||
inputRef?: RefObject<HTMLInputElement>;
|
inputRef?: RefObject<HTMLInputElement>;
|
||||||
// The element to create. Defaults to "input".
|
// The element to create. Defaults to "input".
|
||||||
element?: "input";
|
element?: "input";
|
||||||
componentClass?: undefined;
|
|
||||||
// The input's value. This is a controlled component, so the value is required.
|
// The input's value. This is a controlled component, so the value is required.
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
@ -93,7 +91,6 @@ interface ISelectProps extends IProps, SelectHTMLAttributes<HTMLSelectElement> {
|
||||||
inputRef?: RefObject<HTMLSelectElement>;
|
inputRef?: RefObject<HTMLSelectElement>;
|
||||||
// To define options for a select, use <Field><option ... /></Field>
|
// To define options for a select, use <Field><option ... /></Field>
|
||||||
element: "select";
|
element: "select";
|
||||||
componentClass?: undefined;
|
|
||||||
// The select's value. This is a controlled component, so the value is required.
|
// The select's value. This is a controlled component, so the value is required.
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
@ -102,7 +99,6 @@ interface ITextareaProps extends IProps, TextareaHTMLAttributes<HTMLTextAreaElem
|
||||||
// The ref pass through to the textarea
|
// The ref pass through to the textarea
|
||||||
inputRef?: RefObject<HTMLTextAreaElement>;
|
inputRef?: RefObject<HTMLTextAreaElement>;
|
||||||
element: "textarea";
|
element: "textarea";
|
||||||
componentClass?: undefined;
|
|
||||||
// The textarea's value. This is a controlled component, so the value is required.
|
// The textarea's value. This is a controlled component, so the value is required.
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
@ -111,8 +107,6 @@ export interface INativeOnChangeInputProps extends IProps, InputHTMLAttributes<H
|
||||||
// The ref pass through to the input
|
// The ref pass through to the input
|
||||||
inputRef?: RefObject<HTMLInputElement>;
|
inputRef?: RefObject<HTMLInputElement>;
|
||||||
element: "input";
|
element: "input";
|
||||||
// The custom component to render
|
|
||||||
componentClass: ComponentClass;
|
|
||||||
// The input's value. This is a controlled component, so the value is required.
|
// The input's value. This is a controlled component, so the value is required.
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
@ -248,7 +242,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
|
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
|
||||||
const { element, componentClass, inputRef, prefixComponent, postfixComponent, className, onValidate, children,
|
const { element, inputRef, prefixComponent, postfixComponent, className, onValidate, children,
|
||||||
tooltipContent, forceValidity, tooltipClassName, list, validateOnBlur, validateOnChange, validateOnFocus,
|
tooltipContent, forceValidity, tooltipClassName, list, validateOnBlur, validateOnChange, validateOnFocus,
|
||||||
usePlaceholderAsHint, forceTooltipVisible,
|
usePlaceholderAsHint, forceTooltipVisible,
|
||||||
...inputProps } = this.props;
|
...inputProps } = this.props;
|
||||||
|
@ -265,7 +259,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
||||||
// Appease typescript's inference
|
// Appease typescript's inference
|
||||||
const inputProps_ = { ...inputProps, ref: this.inputRef, list };
|
const inputProps_ = { ...inputProps, ref: this.inputRef, list };
|
||||||
|
|
||||||
const fieldInput = React.createElement(this.props.componentClass || this.props.element, inputProps_, children);
|
const fieldInput = React.createElement(this.props.element, inputProps_, children);
|
||||||
|
|
||||||
let prefixContainer = null;
|
let prefixContainer = null;
|
||||||
if (prefixComponent) {
|
if (prefixComponent) {
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2022 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 React from 'react';
|
|
||||||
|
|
||||||
import { useCombinedRefs } from "../../../hooks/useCombinedRefs";
|
|
||||||
|
|
||||||
interface IProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'onInput'> {
|
|
||||||
onChange?: (event: Event) => void;
|
|
||||||
onInput?: (event: Event) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component restores the native 'onChange' and 'onInput' behavior of
|
|
||||||
* JavaScript which have important differences for certain <input> types. This is
|
|
||||||
* necessary because in React, the `onChange` handler behaves like the native
|
|
||||||
* `oninput` handler and there is no way to tell the difference between an
|
|
||||||
* `input` vs `change` event.
|
|
||||||
*
|
|
||||||
* via https://stackoverflow.com/a/62383569/796832 and
|
|
||||||
* https://github.com/facebook/react/issues/9657#issuecomment-643970199
|
|
||||||
*
|
|
||||||
* See:
|
|
||||||
* - https://reactjs.org/docs/dom-elements.html#onchange
|
|
||||||
* - https://github.com/facebook/react/issues/3964
|
|
||||||
* - https://github.com/facebook/react/issues/9657
|
|
||||||
* - https://github.com/facebook/react/issues/14857
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* We use this for the <input type="date"> date picker so we can distinguish from
|
|
||||||
* a final date picker selection (onChange) vs navigating the months in the date
|
|
||||||
* picker (onInput).
|
|
||||||
*
|
|
||||||
* This is also potentially useful for <input type="range" /> because the native
|
|
||||||
* events behave in such a way that moving the slider around triggers an onInput
|
|
||||||
* event and releasing it triggers onChange.
|
|
||||||
*/
|
|
||||||
const NativeOnChangeInput: React.FC<IProps> = React.forwardRef((props: IProps, ref) => {
|
|
||||||
const registerCallbacks = (input: HTMLInputElement | null) => {
|
|
||||||
if (input) {
|
|
||||||
input.onchange = props.onChange;
|
|
||||||
input.oninput = props.onInput;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return <input
|
|
||||||
ref={useCombinedRefs(registerCallbacks, ref)}
|
|
||||||
{...props}
|
|
||||||
// These are just here so we don't get a read-only input warning from React
|
|
||||||
onChange={() => {}}
|
|
||||||
onInput={() => {}}
|
|
||||||
/>;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default NativeOnChangeInput;
|
|
|
@ -18,7 +18,6 @@ import React, { useState, FormEvent } from 'react';
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import NativeOnChangeInput from "../elements/NativeOnChangeInput";
|
|
||||||
import { RovingAccessibleButton, useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
|
import { RovingAccessibleButton, useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -34,41 +33,11 @@ const JumpToDatePicker: React.FC<IProps> = ({ ts, onDatePicked }: IProps) => {
|
||||||
const dateDefaultValue = `${year}-${month}-${day}`;
|
const dateDefaultValue = `${year}-${month}-${day}`;
|
||||||
|
|
||||||
const [dateValue, setDateValue] = useState(dateDefaultValue);
|
const [dateValue, setDateValue] = useState(dateDefaultValue);
|
||||||
// Whether or not to automatically navigate to the given date after someone
|
|
||||||
// selects a day in the date picker. We want to disable this after someone
|
|
||||||
// starts manually typing in the input instead of picking.
|
|
||||||
const [navigateOnDatePickerSelection, setNavigateOnDatePickerSelection] = useState(true);
|
|
||||||
|
|
||||||
// Since we're using NativeOnChangeInput with native JavaScript behavior, this
|
|
||||||
// tracks the date value changes as they come in.
|
|
||||||
const onDateValueInput = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
|
||||||
setDateValue(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Since we're using NativeOnChangeInput with native JavaScript behavior, the change
|
|
||||||
// event listener will trigger when a date is picked from the date picker
|
|
||||||
// or when the text is fully filled out. In order to not trigger early
|
|
||||||
// as someone is typing out a date, we need to disable when we see keydowns.
|
|
||||||
const onDateValueChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
|
||||||
setDateValue(e.target.value);
|
|
||||||
|
|
||||||
// Don't auto navigate if they were manually typing out a date
|
|
||||||
if (navigateOnDatePickerSelection) {
|
|
||||||
onDatePicked(dateValue);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLInputElement>();
|
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLInputElement>();
|
||||||
|
|
||||||
const onDateInputKeyDown = (e: React.KeyboardEvent): void => {
|
const onDateValueInput = (ev: React.ChangeEvent<HTMLInputElement>) => setDateValue(ev.target.value);
|
||||||
// When we see someone manually typing out a date, disable the auto
|
|
||||||
// submit on change.
|
|
||||||
setNavigateOnDatePickerSelection(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onJumpToDateSubmit = (ev: FormEvent): void => {
|
const onJumpToDateSubmit = (ev: FormEvent): void => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
onDatePicked(dateValue);
|
onDatePicked(dateValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,11 +48,9 @@ const JumpToDatePicker: React.FC<IProps> = ({ ts, onDatePicked }: IProps) => {
|
||||||
>
|
>
|
||||||
<span className="mx_JumpToDatePicker_label">{ _t("Jump to date") }</span>
|
<span className="mx_JumpToDatePicker_label">{ _t("Jump to date") }</span>
|
||||||
<Field
|
<Field
|
||||||
componentClass={NativeOnChangeInput}
|
element="input"
|
||||||
type="date"
|
type="date"
|
||||||
onChange={onDateValueChange}
|
|
||||||
onInput={onDateValueInput}
|
onInput={onDateValueInput}
|
||||||
onKeyDown={onDateInputKeyDown}
|
|
||||||
value={dateValue}
|
value={dateValue}
|
||||||
className="mx_JumpToDatePicker_datePicker"
|
className="mx_JumpToDatePicker_datePicker"
|
||||||
label={_t("Pick a date to jump to")}
|
label={_t("Pick a date to jump to")}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2022 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 { useRef, useEffect } from 'react';
|
|
||||||
|
|
||||||
// Takes in multiple React refs and combines them to reference the same target/element
|
|
||||||
//
|
|
||||||
// via https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd
|
|
||||||
export const useCombinedRefs = (...refs) => {
|
|
||||||
const targetRef = useRef();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
refs.forEach(ref => {
|
|
||||||
if (!ref) return;
|
|
||||||
|
|
||||||
if (typeof ref === 'function') {
|
|
||||||
ref(targetRef.current);
|
|
||||||
} else {
|
|
||||||
ref.current = targetRef.current;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [refs]);
|
|
||||||
|
|
||||||
return targetRef;
|
|
||||||
};
|
|
Loading…
Reference in a new issue