Skip to content
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

Add TextField.escapeToBlur #123

Merged
merged 5 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

## [Unreleased]

### Added

- `<TextField />`:
- Parameter `escapeToBlur`: If set to true the input field blurs/de-focuces when the `Escape` key is pressed.
- `<Modal />`:
- Parameter `modalFocusable: boolean`: When `true` the outer `div` element of the modal can be focused by clicking on it.
This is needed e.g. when key (down, up) events should trigger on the modal in order
to bubble up to its parent elements.

### Fixed

- `<Modal />`:
- Escape key to close does not work anymore after clicking on the backdrop for `canOutsideClickClose=false` and `canEscapeKeyClose=true`.

## [23.2.0] - 2023-07-14

### Added
Expand Down
18 changes: 17 additions & 1 deletion src/components/Dialog/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ export interface ModalProps extends OverlayProps, IOverlayState {
preventBackdrop?: boolean;
/** Optional props for the wrapper div element inside the modal overlay. */
wrapperDivProps?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
/** Make the modal focusable, e.g. when clicking somewhere on it. This is needed, e.g. when capturing key (down, up) events that
* should bubble to the modal's parent elements. */
modalFocusable?: boolean
}

/**
* Displays contents on top of other elements, used to create dialogs.
* For most situations the usage of `SimpleDialog` and `AlertDialog` should be sufficent.
* For most situations the usage of `SimpleDialog` and `AlertDialog` should be sufficient.
* Otherwise this element can be used to create own modal elements and edge cases for modal dialogs.
* Then it is recommended to use the `Card` element inside.
*/
Expand All @@ -40,9 +43,20 @@ export const Modal = ({
canEscapeKeyClose=false,
preventBackdrop=false,
wrapperDivProps,
modalFocusable = true,
...otherProps
}: ModalProps) => {

const backdropProps: React.HTMLProps<HTMLDivElement> | undefined = !canOutsideClickClose && canEscapeKeyClose ? {
...otherProps.backdropProps,
// Escape key won't work anymore otherwise after clicking on the backdrop
tabIndex: 0
} : otherProps.backdropProps

const focusableProps = modalFocusable ? {
tabIndex: 0
} : undefined

const alteredChildren = React.Children.map(children, (child) => {
if ((child as React.ReactElement).type && (child as React.ReactElement).type === Card) {
return React.cloneElement(
Expand All @@ -60,6 +74,7 @@ export const Modal = ({
return (
<BlueprintOverlay
{...otherProps}
backdropProps={backdropProps}
className={overlayClassName}
backdropClassName={`${eccgui}-dialog__backdrop`}
canOutsideClickClose={canOutsideClickClose}
Expand All @@ -71,6 +86,7 @@ export const Modal = ({
className={BlueprintClassNames.DIALOG_CONTAINER}
// this is a workaround because data attribute on SimpleDialog is not correctly routed to the overlay by blueprint js
data-test-id={(otherProps as any)["data-test-id"] ?? "simpleDialogWidget"}
{...focusableProps}
>
<section
className={
Expand Down
38 changes: 32 additions & 6 deletions src/components/TextField/TextField.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from "react";
import React, {KeyboardEventHandler, RefObject} from "react";
import {
InputGroup as BlueprintInputGroup,
Classes as BlueprintClassNames,
Intent as BlueprintIntent,
MaybeElement,
HTMLInputProps,
InputGroup as BlueprintInputGroup,
InputGroupProps2,
Intent as BlueprintIntent,
MaybeElement,
} from "@blueprintjs/core";
import { IntentTypes, Definitions as IntentDefinitions } from "../../common/Intent";
import {Definitions as IntentDefinitions, IntentTypes} from "../../common/Intent";
import Icon from "../Icon/Icon";
import { CLASSPREFIX as eccgui } from "../../configuration/constants";
import {CLASSPREFIX as eccgui} from "../../configuration/constants";
import {ValidIconName} from "../Icon/canonicalIconNames";
import {InvisibleCharacterWarningProps, useTextValidation} from "./useTextValidation";

Expand Down Expand Up @@ -50,6 +50,9 @@ export interface TextFieldProps extends Partial<Omit<InputGroupProps2, "intent"
* If set, allows to be informed of invisible, hard to spot characters in the string value.
*/
invisibleCharacterWarning?: InvisibleCharacterWarningProps

/** If true pressing the Escape key will blur/de-focus the input field. Default: false */
escapeToBlur?: boolean
}

/**
Expand All @@ -64,8 +67,10 @@ export const TextField = ({
fullWidth = true,
leftIcon,
invisibleCharacterWarning,
escapeToBlur = false,
...otherProps
}: TextFieldProps) => {
const inputRef = React.useRef<HTMLInputElement | null>(null)
let deprecatedIntent;
switch (true) {
case hasStatePrimary:
Expand All @@ -84,6 +89,25 @@ export const TextField = ({
break;
}

const handleLabelEscape = React.useCallback(() => {
inputRef.current?.blur()
if(otherProps.inputRef) {
const otherInputRef = otherProps.inputRef as RefObject<HTMLInputElement>
if(otherInputRef.current) {
otherInputRef.current.blur()
}
}
}, [])

const onKeyDown: KeyboardEventHandler<HTMLInputElement> = React.useCallback((event) => {
if(escapeToBlur && event.key === "Escape") {
event.preventDefault()
handleLabelEscape()
return false
}
otherProps.onKeyDown?.(event)
}, [otherProps.onKeyDown, escapeToBlur])
robertisele marked this conversation as resolved.
Show resolved Hide resolved

const {
intent = deprecatedIntent,
...otherBlueprintInputGroupProps
Expand All @@ -110,6 +134,7 @@ export const TextField = ({

return (
<BlueprintInputGroup
inputRef={inputRef}
className={
`${eccgui}-textfield` +
(intent ? ` ${eccgui}-intent--${intent}` : "") +
Expand All @@ -133,6 +158,7 @@ export const TextField = ({
}
dir={"auto"}
onChange={maybeWrappedOnChange}
onKeyDown={otherProps.onKeyDown || escapeToBlur ? onKeyDown : undefined}
/>
);
}
Expand Down
Loading