-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Fix autocompletion in IE11 #6667
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,15 +9,101 @@ import classnames from 'classnames'; | |
| * WordPress dependencies | ||
| */ | ||
| import { Component, createElement } from '@wordpress/element'; | ||
| import { keycodes } from '@wordpress/utils'; | ||
|
|
||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import { diffAriaProps, pickAriaProps } from './aria'; | ||
| import { valueToString } from './format'; | ||
|
|
||
| const { BACKSPACE, DELETE } = keycodes; | ||
|
|
||
| /** | ||
| * Determines whether we need a fix to provide `input` events for contenteditable. | ||
| * | ||
| * @param {Element} editorNode The root editor node. | ||
| * | ||
| * @return {boolean} A boolean indicating whether the fix is needed. | ||
| */ | ||
| function needsInternetExplorerInputFix( editorNode ) { | ||
| return ( | ||
| // Rely on userAgent in the absence of a reasonable feature test for contenteditable `input` events. | ||
| /Trident/.test( window.navigator.userAgent ) && | ||
| // IE11 dispatches input events for `<input>` and `<textarea>`. | ||
| ! /input/i.test( editorNode.tagName ) && | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we check this? A rich text field can never be an input or textarea. |
||
| ! /textarea/i.test( editorNode.tagName ) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Applies a fix that provides `input` events for contenteditable in Internet Explorer. | ||
| * | ||
| * @param {Element} editorNode The root editor node. | ||
| * | ||
| * @return {Function} A function to remove the fix (for cleanup). | ||
| */ | ||
| function applyInternetExplorerInputFix( editorNode ) { | ||
| /** | ||
| * Dispatches `input` events in response to `textinput` events. | ||
| * | ||
| * IE provides a `textinput` event that is similar to an `input` event, | ||
| * and we use it to manually dispatch an `input` event. | ||
| * `textinput` is dispatched for text entry but for not deletions. | ||
| * | ||
| * @param {Event} textInputEvent An Internet Explorer `textinput` event. | ||
| */ | ||
| function mapTextInputEvent( textInputEvent ) { | ||
| textInputEvent.stopImmediatePropagation(); | ||
|
|
||
| const inputEvent = document.createEvent( 'Event' ); | ||
| inputEvent.initEvent( 'input', true, false ); | ||
| inputEvent.data = textInputEvent.data; | ||
| textInputEvent.target.dispatchEvent( inputEvent ); | ||
| } | ||
|
|
||
| /** | ||
| * Dispatches `input` events in response to Delete and Backspace keyup. | ||
| * | ||
| * It would be better dispatch an `input` event after each deleting | ||
| * `keydown` because the DOM is updated after each, but it is challenging | ||
| * to determine the right time to dispatch `input` since propagation of | ||
| * `keydown` can be stopped at any point. | ||
| * | ||
| * It's easier to listen for `keyup` in the capture phase and dispatch | ||
| * `input` before `keyup` propagates further. It's not perfect, but should | ||
| * be good enough. | ||
| * | ||
| * @param {KeyboardEvent} keyUp | ||
| * @param {Node} keyUp.target The event target. | ||
| * @param {number} keyUp.keyCode The key code. | ||
| */ | ||
| function mapDeletionKeyUpEvents( { target, keyCode } ) { | ||
| const isDeletion = BACKSPACE === keyCode || DELETE === keyCode; | ||
|
|
||
| if ( isDeletion && editorNode.contains( target ) ) { | ||
| const inputEvent = document.createEvent( 'Event' ); | ||
| inputEvent.initEvent( 'input', true, false ); | ||
| inputEvent.data = null; | ||
| target.dispatchEvent( inputEvent ); | ||
| } | ||
| } | ||
|
|
||
| editorNode.addEventListener( 'textinput', mapTextInputEvent ); | ||
| document.addEventListener( 'keyup', mapDeletionKeyUpEvents, true ); | ||
| return function removeInternetExplorerInputFix() { | ||
| editorNode.removeEventListener( 'textinput', mapTextInputEvent ); | ||
| document.removeEventListener( 'keyup', mapDeletionKeyUpEvents, true ); | ||
| }; | ||
| } | ||
|
|
||
| const IS_PLACEHOLDER_VISIBLE_ATTR_NAME = 'data-is-placeholder-visible'; | ||
| export default class TinyMCE extends Component { | ||
| constructor() { | ||
| super(); | ||
| this.bindEditorNode = this.bindEditorNode.bind( this ); | ||
| } | ||
|
|
||
| componentDidMount() { | ||
| this.initialize(); | ||
| } | ||
|
|
@@ -96,6 +182,23 @@ export default class TinyMCE extends Component { | |
| } ); | ||
| } | ||
|
|
||
| bindEditorNode( editorNode ) { | ||
| this.editorNode = editorNode; | ||
|
|
||
| /** | ||
| * A ref function can be used for cleanup because React calls it with | ||
| * `null` when unmounting. | ||
| */ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, that's cool!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I love being able to keep allocation and cleanup in one place while also handling real situations where a ref is removed and re-added during the life of a component.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we just use Related: The latest version of React includes a new https://reactjs.org/docs/refs-and-the-dom.html#creating-refs |
||
| if ( this.removeInternetExplorerInputFix ) { | ||
| this.removeInternetExplorerInputFix(); | ||
| this.removeInternetExplorerInputFix = null; | ||
| } | ||
|
|
||
| if ( editorNode && needsInternetExplorerInputFix( editorNode ) ) { | ||
| this.removeInternetExplorerInputFix = applyInternetExplorerInputFix( editorNode ); | ||
| } | ||
| } | ||
|
|
||
| render() { | ||
| const { tagName = 'div', style, defaultValue, className, isPlaceholderVisible, format } = this.props; | ||
| const ariaProps = pickAriaProps( this.props ); | ||
|
|
@@ -112,7 +215,7 @@ export default class TinyMCE extends Component { | |
| className: classnames( className, 'editor-rich-text__tinymce' ), | ||
| contentEditable: true, | ||
| [ IS_PLACEHOLDER_VISIBLE_ATTR_NAME ]: isPlaceholderVisible, | ||
| ref: ( node ) => this.editorNode = node, | ||
| ref: this.bindEditorNode, | ||
| style, | ||
| suppressContentEditableWarning: true, | ||
| dangerouslySetInnerHTML: { __html: valueToString( defaultValue, format ) }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we borrow what Modernizr does here? Or is that for a different
inputevent?https://github.com/Modernizr/Modernizr/blob/master/feature-detects/event/oninput.js
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about that too. Unfortunately, the test returns true for even a non-contenteditable
<div>in IE11 because of the first part that sets anoninputattribute and checks the type of the element'soninputproperty. The browser does support theinputevent but doesn't dispatch it forcontenteditabletext input.I tried to adapt the latter half of the test that dispatches a KeyboardEvent and listens for
inputbut didn't have any luck getting it to work in IE11. It's possible I missed something but didn't think I should spend more time on it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, thank you for taking the time to look and make this suggestion, @noisysocks . I actually forgot that I'd looked at this until I worked through it again. :)