-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3b8d568
commit 102811e
Showing
12 changed files
with
972 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { memo } from '@wordpress/element'; | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { BackdropUI } from './styles'; | ||
|
||
function Backdrop( { disabled = false, isFocused = false } ) { | ||
return ( | ||
<BackdropUI | ||
aria-hidden="true" | ||
className="components-input-control__backdrop" | ||
disabled={ disabled } | ||
isFocused={ isFocused } | ||
/> | ||
); | ||
} | ||
|
||
const MemoizedBackdrop = memo( Backdrop ); | ||
|
||
export default MemoizedBackdrop; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { noop } from 'lodash'; | ||
import { useDrag } from 'react-use-gesture'; | ||
// eslint-disable-next-line no-restricted-imports | ||
import type { Ref, SyntheticEvent, PointerEvent, MouseEvent } from 'react'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useRef } from '@wordpress/element'; | ||
import { UP, DOWN, ENTER } from '@wordpress/keycodes'; | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { useDragCursor } from './utils'; | ||
import { Input as InputView, Container, Prefix, Suffix } from './styles'; | ||
import Backdrop from './backdrop'; | ||
import { useInputControlStateReducer } from './reducer/reducer'; | ||
import { isValueEmpty } from '../utils/values'; | ||
import { useUpdateEffect } from '../utils'; | ||
import { | ||
contextConnect, | ||
useContextSystem, | ||
PolymorphicComponentProps, | ||
} from '../ui/context'; | ||
import type { Props } from './types'; | ||
|
||
function Input( | ||
props: PolymorphicComponentProps< Props, 'input' >, | ||
forwardedRef: Ref< any > | ||
) { | ||
const { | ||
disabled = false, | ||
dragDirection = 'n', | ||
dragThreshold = 10, | ||
id, | ||
isDragEnabled = false, | ||
isFocused, | ||
isPressEnterToChange = false, | ||
onBlur = noop, | ||
onChange = noop, | ||
onDrag = noop, | ||
onDragEnd = noop, | ||
onDragStart = noop, | ||
onFocus = noop, | ||
onKeyDown = noop, | ||
onValidate = noop, | ||
size = 'default', | ||
setIsFocused, | ||
stateReducer = ( state ) => state, | ||
value: valueProp, | ||
type, | ||
hideLabelFromVision = false, | ||
__unstableInputWidth, | ||
labelPosition, | ||
prefix, | ||
suffix, | ||
...otherProps | ||
} = useContextSystem( props, 'Input' ); | ||
|
||
const { | ||
// State | ||
state, | ||
// Actions | ||
change, | ||
commit, | ||
drag, | ||
dragEnd, | ||
dragStart, | ||
invalidate, | ||
pressDown, | ||
pressEnter, | ||
pressUp, | ||
reset, | ||
update, | ||
} = useInputControlStateReducer( stateReducer, { | ||
isDragEnabled, | ||
value: valueProp, | ||
isPressEnterToChange, | ||
} ); | ||
|
||
const { _event, value, isDragging, isDirty } = state; | ||
const wasDirtyOnBlur = useRef( false ); | ||
|
||
const dragCursor = useDragCursor( isDragging, dragDirection ); | ||
|
||
/* | ||
* Handles synchronization of external and internal value state. | ||
* If not focused and did not hold a dirty value[1] on blur | ||
* updates the value from the props. Otherwise if not holding | ||
* a dirty value[1] propagates the value and event through onChange. | ||
* [1] value is only made dirty if isPressEnterToChange is true | ||
*/ | ||
useUpdateEffect( () => { | ||
if ( valueProp === value ) { | ||
return; | ||
} | ||
if ( ! isFocused && ! wasDirtyOnBlur.current ) { | ||
update( valueProp, _event as SyntheticEvent ); | ||
} else if ( ! isDirty ) { | ||
onChange( value, { event: _event } ); | ||
wasDirtyOnBlur.current = false; | ||
} | ||
}, [ value, isDirty, isFocused, valueProp ] ); | ||
|
||
const handleOnBlur = ( event ) => { | ||
onBlur( event ); | ||
setIsFocused( false ); | ||
|
||
/** | ||
* If isPressEnterToChange is set, this commits the value to | ||
* the onChange callback. | ||
*/ | ||
if ( isPressEnterToChange && isDirty ) { | ||
wasDirtyOnBlur.current = true; | ||
if ( ! isValueEmpty( value ) ) { | ||
handleOnCommit( event ); | ||
} else { | ||
reset( valueProp, event ); | ||
} | ||
} | ||
}; | ||
|
||
const handleOnFocus = ( event ) => { | ||
onFocus( event ); | ||
setIsFocused( true ); | ||
}; | ||
|
||
const handleOnChange = ( event ) => { | ||
const nextValue = event.target.value; | ||
change( nextValue, event ); | ||
}; | ||
|
||
const handleOnCommit = ( event ) => { | ||
const nextValue = event.target.value; | ||
|
||
try { | ||
onValidate( nextValue ); | ||
commit( nextValue, event ); | ||
} catch ( err ) { | ||
invalidate( err, event ); | ||
} | ||
}; | ||
|
||
const handleOnKeyDown = ( event ) => { | ||
const { keyCode } = event; | ||
onKeyDown( event ); | ||
|
||
switch ( keyCode ) { | ||
case UP: | ||
pressUp( event ); | ||
break; | ||
|
||
case DOWN: | ||
pressDown( event ); | ||
break; | ||
|
||
case ENTER: | ||
pressEnter( event ); | ||
|
||
if ( isPressEnterToChange ) { | ||
event.preventDefault(); | ||
handleOnCommit( event ); | ||
} | ||
break; | ||
} | ||
}; | ||
|
||
const dragGestureProps = useDrag< PointerEvent< HTMLElement > >( | ||
( dragProps ) => { | ||
const { distance, dragging, event } = dragProps; | ||
// The event is persisted to prevent errors in components using this | ||
// to check if a modifier key was held while dragging. | ||
( event as SyntheticEvent ).persist(); | ||
|
||
if ( ! distance ) return; | ||
event.stopPropagation(); | ||
|
||
/** | ||
* Quick return if no longer dragging. | ||
* This prevents unnecessary value calculations. | ||
*/ | ||
if ( ! dragging ) { | ||
onDragEnd( dragProps ); | ||
dragEnd( dragProps ); | ||
return; | ||
} | ||
|
||
onDrag( dragProps ); | ||
drag( dragProps ); | ||
|
||
if ( ! isDragging ) { | ||
onDragStart( dragProps ); | ||
dragStart( dragProps ); | ||
} | ||
}, | ||
{ | ||
threshold: dragThreshold, | ||
enabled: isDragEnabled, | ||
} | ||
); | ||
|
||
const dragProps = isDragEnabled ? dragGestureProps() : {}; | ||
/* | ||
* Works around the odd UA (e.g. Firefox) that does not focus inputs of | ||
* type=number when their spinner arrows are pressed. | ||
*/ | ||
let handleOnMouseDown: | ||
| undefined | ||
| ( ( event: MouseEvent< HTMLInputElement > ) => void ); | ||
if ( type === 'number' ) { | ||
handleOnMouseDown = ( event ) => { | ||
props.onMouseDown?.( event ); | ||
if ( | ||
event.target !== | ||
( event.target as HTMLInputElement ).ownerDocument.activeElement | ||
) { | ||
( event.target as HTMLInputElement ).focus(); | ||
} | ||
}; | ||
} | ||
|
||
return ( | ||
<Container | ||
__unstableInputWidth={ __unstableInputWidth } | ||
className="components-input-control__container" | ||
disabled={ disabled } | ||
hideLabel={ hideLabelFromVision } | ||
labelPosition={ labelPosition } | ||
> | ||
{ prefix && ( | ||
<Prefix className="components-input-control__prefix"> | ||
{ prefix } | ||
</Prefix> | ||
) } | ||
<InputView | ||
{ ...otherProps } | ||
{ ...dragProps } | ||
className="components-input-control__input" | ||
disabled={ disabled } | ||
dragCursor={ dragCursor } | ||
isDragging={ isDragging } | ||
id={ id } | ||
onBlur={ handleOnBlur } | ||
onChange={ handleOnChange } | ||
onFocus={ handleOnFocus } | ||
onKeyDown={ handleOnKeyDown } | ||
onMouseDown={ handleOnMouseDown } | ||
ref={ forwardedRef } | ||
inputSize={ size } | ||
value={ value } | ||
type={ type } | ||
/> | ||
{ suffix && ( | ||
<Suffix className="components-input-control__suffix"> | ||
{ suffix } | ||
</Suffix> | ||
) } | ||
<Backdrop disabled={ disabled } isFocused={ isFocused } /> | ||
</Container> | ||
); | ||
} | ||
|
||
const ConnectedInput = contextConnect( Input, 'Input' ); | ||
|
||
export default ConnectedInput; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as Input } from './component'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
// eslint-disable-next-line no-restricted-imports | ||
import type { SyntheticEvent } from 'react'; | ||
|
||
export const CHANGE = 'CHANGE'; | ||
export const COMMIT = 'COMMIT'; | ||
export const DRAG_END = 'DRAG_END'; | ||
export const DRAG_START = 'DRAG_START'; | ||
export const DRAG = 'DRAG'; | ||
export const INVALIDATE = 'INVALIDATE'; | ||
export const PRESS_DOWN = 'PRESS_DOWN'; | ||
export const PRESS_ENTER = 'PRESS_ENTER'; | ||
export const PRESS_UP = 'PRESS_UP'; | ||
export const RESET = 'RESET'; | ||
export const UPDATE = 'UPDATE'; | ||
|
||
interface EventPayload { | ||
event?: SyntheticEvent; | ||
} | ||
|
||
interface Action< Type, ExtraPayload = {} > { | ||
type: Type; | ||
payload: EventPayload & ExtraPayload; | ||
} | ||
|
||
interface ValuePayload { | ||
value: string; | ||
} | ||
|
||
export type ChangeAction = Action< typeof CHANGE, ValuePayload >; | ||
export type CommitAction = Action< typeof COMMIT, ValuePayload >; | ||
export type PressUpAction = Action< typeof PRESS_UP >; | ||
export type PressDownAction = Action< typeof PRESS_DOWN >; | ||
export type PressEnterAction = Action< typeof PRESS_ENTER >; | ||
export type DragStartAction = Action< typeof DRAG_START >; | ||
export type DragEndAction = Action< typeof DRAG_END >; | ||
export type DragAction = Action< typeof DRAG >; | ||
export type ResetAction = Action< typeof RESET, Partial< ValuePayload > >; | ||
export type UpdateAction = Action< typeof UPDATE, ValuePayload >; | ||
export type InvalidateAction = Action< | ||
typeof INVALIDATE, | ||
{ error: Error | null } | ||
>; | ||
|
||
export type ChangeEventAction = | ||
| ChangeAction | ||
| ResetAction | ||
| CommitAction | ||
| UpdateAction; | ||
|
||
export type DragEventAction = DragStartAction | DragEndAction | DragAction; | ||
|
||
export type KeyEventAction = PressDownAction | PressUpAction | PressEnterAction; | ||
|
||
export type InputAction = | ||
| ChangeEventAction | ||
| KeyEventAction | ||
| DragEventAction | ||
| InvalidateAction; |
Oops, something went wrong.