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 title to gutenberg mobile #13199

Merged
merged 15 commits into from Jan 24, 2019
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,139 @@
/**
* External dependencies
*/
import { includes } from 'lodash';
import { View } from 'react-native';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';

/**
* Input types which are classified as button types, for use in considering
* whether element is a (focus-normalized) button.
*
* @type {string[]}
*/
const INPUT_BUTTON_TYPES = [
'button',
'submit',
];

/**
* Returns true if the given element is a button element subject to focus
* normalization, or false otherwise.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
*
* @param {Element} element Element to test.
*
* @return {boolean} Whether element is a button.
*/
function isFocusNormalizedButton( element ) {
switch ( element.nodeName ) {
case 'A':
case 'BUTTON':
return true;

case 'INPUT':
return includes( INPUT_BUTTON_TYPES, element.type );
}

return false;
}

export default createHigherOrderComponent(
( WrappedComponent ) => {
return class extends Component {
constructor() {
super( ...arguments );

this.bindNode = this.bindNode.bind( this );
this.cancelBlurCheck = this.cancelBlurCheck.bind( this );
this.queueBlurCheck = this.queueBlurCheck.bind( this );
this.normalizeButtonFocus = this.normalizeButtonFocus.bind( this );
}

componentWillUnmount() {
this.cancelBlurCheck();
}

bindNode( node ) {
if ( node ) {
this.node = node;
} else {
delete this.node;
this.cancelBlurCheck();
}
}

queueBlurCheck( event ) {
// React does not allow using an event reference asynchronously
// due to recycling behavior, except when explicitly persisted.
event.persist();

// Skip blur check if clicking button. See `normalizeButtonFocus`.
if ( this.preventBlurCheck ) {
return;
}

this.blurCheckTimeout = setTimeout( () => {
if ( 'function' === typeof this.node.handleFocusOutside ) {
this.node.handleFocusOutside( event );
}
}, 0 );
}

cancelBlurCheck() {
clearTimeout( this.blurCheckTimeout );
}

/**
* Handles a mousedown or mouseup event to respectively assign and
* unassign a flag for preventing blur check on button elements. Some
* browsers, namely Firefox and Safari, do not emit a focus event on
* button elements when clicked, while others do. The logic here
* intends to normalize this as treating click on buttons as focus.
*
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
*
* @param {MouseEvent} event Event for mousedown or mouseup.
*/
normalizeButtonFocus( event ) {
const { type, target } = event;

const isInteractionEnd = includes( [ 'mouseup', 'touchend' ], type );

if ( isInteractionEnd ) {
this.preventBlurCheck = false;
} else if ( isFocusNormalizedButton( target ) ) {
this.preventBlurCheck = true;
}
}

render() {
// Disable reason: See `normalizeButtonFocus` for browser-specific
// focus event normalization.

/* eslint-disable jsx-a11y/no-static-element-interactions */
return (
<View
onFocus={ this.cancelBlurCheck }
onMouseDown={ this.normalizeButtonFocus }
onMouseUp={ this.normalizeButtonFocus }
onTouchStart={ this.normalizeButtonFocus }
onTouchEnd={ this.normalizeButtonFocus }
onBlur={ this.queueBlurCheck }
>
<WrappedComponent
ref={ this.bindNode }
{ ...this.props } />
</View>
);
/* eslint-enable jsx-a11y/no-static-element-interactions */
}
};
}, 'withFocusOutside'
);
1 change: 1 addition & 0 deletions packages/components/src/index.native.js
Expand Up @@ -9,3 +9,4 @@ export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot

// Higher-Order Components
export { default as withFilters } from './higher-order/with-filters';
export { default as withFocusOutside } from './higher-order/with-focus-outside';
1 change: 1 addition & 0 deletions packages/editor/src/components/index.native.js
Expand Up @@ -7,5 +7,6 @@ export { default as BlockFormatControls } from './block-format-controls';
export { default as BlockControls } from './block-controls';
export { default as BlockEdit } from './block-edit';
export { default as DefaultBlockAppender } from './default-block-appender';
export { default as PostTitle } from './post-title';
export { default as EditorHistoryRedo } from './editor-history/redo';
export { default as EditorHistoryUndo } from './editor-history/undo';
84 changes: 84 additions & 0 deletions packages/editor/src/components/post-title/index.native.js
@@ -0,0 +1,84 @@
/**
* External dependencies
*/
import { TextInput } from 'react-native';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { decodeEntities } from '@wordpress/html-entities';
import { withDispatch } from '@wordpress/data';
import { withFocusOutside } from '@wordpress/components';
import { withInstanceId, compose } from '@wordpress/compose';

class PostTitle extends Component {
constructor() {
super( ...arguments );

this.onChange = this.onChange.bind( this );
this.onSelect = this.onSelect.bind( this );
this.onUnselect = this.onUnselect.bind( this );

this.state = {
isSelected: false,
};
}

handleFocusOutside() {
this.onUnselect();
}

onSelect() {
this.setState( { isSelected: true } );
this.props.clearSelectedBlock();
pinarol marked this conversation as resolved.
Show resolved Hide resolved
}

onUnselect() {
pinarol marked this conversation as resolved.
Show resolved Hide resolved
this.setState( { isSelected: false } );
}

onChange( title ) {
this.props.onUpdate( title );
}

render() {
const {
placeholder,
style,
title,
} = this.props;

const decodedPlaceholder = decodeEntities( placeholder );

return (
<TextInput
blurOnSubmit={ true }
textAlignVertical="top"
multiline
numberOfLines={ 0 }
onChangeText={ this.onChange }
onFocus={ this.onSelect }
placeholder={ decodedPlaceholder }
style={ style }
value={ title }>
</TextInput>
);
}
}

const applyWithDispatch = withDispatch( ( dispatch ) => {
const {
clearSelectedBlock,
} = dispatch( 'core/editor' );

return {
clearSelectedBlock,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this was the thing I was referring to by disptaching to store, just like this one we can also inject onUpdate to props and use editPost( { title } ) to update the post in the store, just like the web side here:

} );

export default compose(
applyWithDispatch,
withInstanceId,
withFocusOutside
)( PostTitle );