Fix reply button on media modal not giving focus to compose form (#17626)
* Avoid compose form and modal management fighting for focus * Fix reply button on media modal footer not giving focus to compose form
This commit is contained in:
		
							parent
							
								
									d4592bbfcd
								
							
						
					
					
						commit
						2cd31b3177
					
				| @ -9,9 +9,10 @@ export function openModal(type, props) { | |||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function closeModal(type) { | export function closeModal(type, options = { ignoreFocus: false }) { | ||||||
|   return { |   return { | ||||||
|     type: MODAL_CLOSE, |     type: MODAL_CLOSE, | ||||||
|     modalType: type, |     modalType: type, | ||||||
|  |     ignoreFocus: options.ignoreFocus, | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ export default class ModalRoot extends React.PureComponent { | |||||||
|       g: PropTypes.number, |       g: PropTypes.number, | ||||||
|       b: PropTypes.number, |       b: PropTypes.number, | ||||||
|     }), |     }), | ||||||
|  |     ignoreFocus: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   activeElement = this.props.children ? document.activeElement : null; |   activeElement = this.props.children ? document.activeElement : null; | ||||||
| @ -72,7 +73,9 @@ export default class ModalRoot extends React.PureComponent { | |||||||
|       // immediately selectable, we have to wait for observers to run, as
 |       // immediately selectable, we have to wait for observers to run, as
 | ||||||
|       // described in https://github.com/WICG/inert#performance-and-gotchas
 |       // described in https://github.com/WICG/inert#performance-and-gotchas
 | ||||||
|       Promise.resolve().then(() => { |       Promise.resolve().then(() => { | ||||||
|  |         if (!this.props.ignoreFocus) { | ||||||
|           this.activeElement.focus({ preventScroll: true }); |           this.activeElement.focus({ preventScroll: true }); | ||||||
|  |         } | ||||||
|         this.activeElement = null; |         this.activeElement = null; | ||||||
|       }).catch(console.error); |       }).catch(console.error); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -163,8 +163,13 @@ class ComposeForm extends ImmutablePureComponent { | |||||||
|         selectionStart = selectionEnd; |         selectionStart = selectionEnd; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |       // Because of the wicg-inert polyfill, the activeElement may not be
 | ||||||
|  |       // immediately selectable, we have to wait for observers to run, as
 | ||||||
|  |       // described in https://github.com/WICG/inert#performance-and-gotchas
 | ||||||
|  |       Promise.resolve().then(() => { | ||||||
|         this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd); |         this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd); | ||||||
|         this.autosuggestTextarea.textarea.focus(); |         this.autosuggestTextarea.textarea.focus(); | ||||||
|  |       }).catch(console.error); | ||||||
|     } else if(prevProps.isSubmitting && !this.props.isSubmitting) { |     } else if(prevProps.isSubmitting && !this.props.isSubmitting) { | ||||||
|       this.autosuggestTextarea.textarea.focus(); |       this.autosuggestTextarea.textarea.focus(); | ||||||
|     } else if (this.props.spoiler !== prevProps.spoiler) { |     } else if (this.props.spoiler !== prevProps.spoiler) { | ||||||
|  | |||||||
| @ -60,7 +60,7 @@ class Footer extends ImmutablePureComponent { | |||||||
|     const { router } = this.context; |     const { router } = this.context; | ||||||
| 
 | 
 | ||||||
|     if (onClose) { |     if (onClose) { | ||||||
|       onClose(); |       onClose(true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     dispatch(replyCompose(status, router.history)); |     dispatch(replyCompose(status, router.history)); | ||||||
|  | |||||||
| @ -45,6 +45,7 @@ export default class ModalRoot extends React.PureComponent { | |||||||
|     type: PropTypes.string, |     type: PropTypes.string, | ||||||
|     props: PropTypes.object, |     props: PropTypes.object, | ||||||
|     onClose: PropTypes.func.isRequired, |     onClose: PropTypes.func.isRequired, | ||||||
|  |     ignoreFocus: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   state = { |   state = { | ||||||
| @ -79,7 +80,7 @@ export default class ModalRoot extends React.PureComponent { | |||||||
|     return <BundleModalError {...props} onClose={onClose} />; |     return <BundleModalError {...props} onClose={onClose} />; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleClose = () => { |   handleClose = (ignoreFocus = false) => { | ||||||
|     const { onClose } = this.props; |     const { onClose } = this.props; | ||||||
|     let message = null; |     let message = null; | ||||||
|     try { |     try { | ||||||
| @ -89,7 +90,7 @@ export default class ModalRoot extends React.PureComponent { | |||||||
|       // isn't set.
 |       // isn't set.
 | ||||||
|       // This would be much smoother with react-intl 3+ and `forwardRef`.
 |       // This would be much smoother with react-intl 3+ and `forwardRef`.
 | ||||||
|     } |     } | ||||||
|     onClose(message); |     onClose(message, ignoreFocus); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setModalRef = (c) => { |   setModalRef = (c) => { | ||||||
| @ -97,12 +98,12 @@ export default class ModalRoot extends React.PureComponent { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { type, props } = this.props; |     const { type, props, ignoreFocus } = this.props; | ||||||
|     const { backgroundColor } = this.state; |     const { backgroundColor } = this.state; | ||||||
|     const visible = !!type; |     const visible = !!type; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Base backgroundColor={backgroundColor} onClose={this.handleClose}> |       <Base backgroundColor={backgroundColor} onClose={this.handleClose} ignoreFocus={ignoreFocus}> | ||||||
|         {visible && ( |         {visible && ( | ||||||
|           <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}> |           <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}> | ||||||
|             {(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />} |             {(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />} | ||||||
|  | |||||||
| @ -3,22 +3,23 @@ import { openModal, closeModal } from '../../../actions/modal'; | |||||||
| import ModalRoot from '../components/modal_root'; | import ModalRoot from '../components/modal_root'; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
|   type: state.getIn(['modal', 0, 'modalType'], null), |   ignoreFocus: state.getIn(['modal', 'ignoreFocus']), | ||||||
|   props: state.getIn(['modal', 0, 'modalProps'], {}), |   type: state.getIn(['modal', 'stack', 0, 'modalType'], null), | ||||||
|  |   props: state.getIn(['modal', 'stack', 0, 'modalProps'], {}), | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const mapDispatchToProps = dispatch => ({ | const mapDispatchToProps = dispatch => ({ | ||||||
|   onClose (confirmationMessage) { |   onClose (confirmationMessage, ignoreFocus = false) { | ||||||
|     if (confirmationMessage) { |     if (confirmationMessage) { | ||||||
|       dispatch( |       dispatch( | ||||||
|         openModal('CONFIRM', { |         openModal('CONFIRM', { | ||||||
|           message: confirmationMessage.message, |           message: confirmationMessage.message, | ||||||
|           confirm: confirmationMessage.confirm, |           confirm: confirmationMessage.confirm, | ||||||
|           onConfirm: () => dispatch(closeModal()), |           onConfirm: () => dispatch(closeModal(undefined, { ignoreFocus })), | ||||||
|         }), |         }), | ||||||
|       ); |       ); | ||||||
|     } else { |     } else { | ||||||
|       dispatch(closeModal()); |       dispatch(closeModal(undefined, { ignoreFocus })); | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -3,16 +3,36 @@ import { TIMELINE_DELETE } from '../actions/timelines'; | |||||||
| import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose'; | import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose'; | ||||||
| import { Stack as ImmutableStack, Map as ImmutableMap } from 'immutable'; | import { Stack as ImmutableStack, Map as ImmutableMap } from 'immutable'; | ||||||
| 
 | 
 | ||||||
| export default function modal(state = ImmutableStack(), action) { | const initialState = ImmutableMap({ | ||||||
|  |   ignoreFocus: false, | ||||||
|  |   stack: ImmutableStack(), | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const popModal = (state, { modalType, ignoreFocus }) => { | ||||||
|  |   if (modalType === undefined || modalType === state.getIn(['stack', 0, 'modalType'])) { | ||||||
|  |     return state.set('ignoreFocus', !!ignoreFocus).update('stack', stack => stack.shift()); | ||||||
|  |   } else { | ||||||
|  |     return state; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const pushModal = (state, modalType, modalProps) => { | ||||||
|  |   return state.withMutations(map => { | ||||||
|  |     map.set('ignoreFocus', false); | ||||||
|  |     map.update('stack', stack => stack.unshift(ImmutableMap({ modalType, modalProps }))); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default function modal(state = initialState, action) { | ||||||
|   switch(action.type) { |   switch(action.type) { | ||||||
|   case MODAL_OPEN: |   case MODAL_OPEN: | ||||||
|     return state.unshift(ImmutableMap({ modalType: action.modalType, modalProps: action.modalProps })); |     return pushModal(state, action.modalType, action.modalProps); | ||||||
|   case MODAL_CLOSE: |   case MODAL_CLOSE: | ||||||
|     return (action.modalType === undefined || action.modalType === state.getIn([0, 'modalType'])) ? state.shift() : state; |     return popModal(state, action); | ||||||
|   case COMPOSE_UPLOAD_CHANGE_SUCCESS: |   case COMPOSE_UPLOAD_CHANGE_SUCCESS: | ||||||
|     return state.getIn([0, 'modalType']) === 'FOCAL_POINT' ? state.shift() : state; |     return popModal(state, { modalType: 'FOCAL_POINT', ignoreFocus: false }); | ||||||
|   case TIMELINE_DELETE: |   case TIMELINE_DELETE: | ||||||
|     return state.filterNot((modal) => modal.get('modalProps').statusId === action.id); |     return state.update('stack', stack => stack.filterNot((modal) => modal.get('modalProps').statusId === action.id)); | ||||||
|   default: |   default: | ||||||
|     return state; |     return state; | ||||||
|   } |   } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user