-
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.
Rewrite FocusableIframe as hook API (#26753)
- Loading branch information
Showing
10 changed files
with
126 additions
and
118 deletions.
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 |
---|---|---|
@@ -1,96 +1,75 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useMergeRefs, useFocusableIframe } from '@wordpress/compose'; | ||
import { useRef, useEffect, useMemo } from '@wordpress/element'; | ||
|
||
/** @typedef {import('@wordpress/element').WPSyntheticEvent} WPSyntheticEvent */ | ||
|
||
const attributeMap = { | ||
class: 'className', | ||
frameborder: 'frameBorder', | ||
marginheight: 'marginHeight', | ||
marginwidth: 'marginWidth', | ||
}; | ||
|
||
export default function WpEmbedPreview( { html } ) { | ||
const ref = useRef(); | ||
const props = useMemo( () => { | ||
const doc = new window.DOMParser().parseFromString( html, 'text/html' ); | ||
const iframe = doc.querySelector( 'iframe' ); | ||
const iframeProps = {}; | ||
|
||
if ( ! iframe ) return iframeProps; | ||
|
||
Array.from( iframe.attributes ).forEach( ( { name, value } ) => { | ||
if ( name === 'style' ) return; | ||
iframeProps[ attributeMap[ name ] || name ] = value; | ||
} ); | ||
|
||
return iframeProps; | ||
}, [ html ] ); | ||
|
||
useEffect( () => { | ||
const { ownerDocument } = ref.current; | ||
const { defaultView } = ownerDocument; | ||
|
||
/** | ||
* Checks for WordPress embed events signaling the height change when iframe | ||
* content loads or iframe's window is resized. The event is sent from | ||
* WordPress core via the window.postMessage API. | ||
* Checks for WordPress embed events signaling the height change when | ||
* iframe content loads or iframe's window is resized. The event is | ||
* sent from WordPress core via the window.postMessage API. | ||
* | ||
* References: | ||
* window.postMessage: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage | ||
* WordPress core embed-template on load: https://github.com/WordPress/WordPress/blob/HEAD/wp-includes/js/wp-embed-template.js#L143 | ||
* WordPress core embed-template on resize: https://github.com/WordPress/WordPress/blob/HEAD/wp-includes/js/wp-embed-template.js#L187 | ||
* window.postMessage: | ||
* https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage | ||
* WordPress core embed-template on load: | ||
* https://github.com/WordPress/WordPress/blob/HEAD/wp-includes/js/wp-embed-template.js#L143 | ||
* WordPress core embed-template on resize: | ||
* https://github.com/WordPress/WordPress/blob/HEAD/wp-includes/js/wp-embed-template.js#L187 | ||
* | ||
* @param {WPSyntheticEvent} event Message event. | ||
* @param {MessageEvent} event Message event. | ||
*/ | ||
function resizeWPembeds( { data: { secret, message, value } = {} } ) { | ||
if ( | ||
[ secret, message, value ].some( | ||
( attribute ) => ! attribute | ||
) || | ||
message !== 'height' | ||
) { | ||
return; | ||
} | ||
|
||
ownerDocument | ||
.querySelectorAll( `iframe[data-secret="${ secret }"` ) | ||
.forEach( ( iframe ) => { | ||
if ( +iframe.height !== value ) { | ||
iframe.height = value; | ||
} | ||
} ); | ||
} | ||
|
||
/** | ||
* Checks whether the wp embed iframe is the activeElement, | ||
* if it is dispatch a focus event. | ||
*/ | ||
function checkFocus() { | ||
const { activeElement } = ownerDocument; | ||
|
||
if ( | ||
activeElement.tagName !== 'IFRAME' || | ||
activeElement.parentNode !== ref.current | ||
) { | ||
if ( message !== 'height' || secret !== props[ 'data-secret' ] ) { | ||
return; | ||
} | ||
|
||
activeElement.focus(); | ||
ref.current.height = value; | ||
} | ||
|
||
defaultView.addEventListener( 'message', resizeWPembeds ); | ||
defaultView.addEventListener( 'blur', checkFocus ); | ||
|
||
return () => { | ||
defaultView.removeEventListener( 'message', resizeWPembeds ); | ||
defaultView.removeEventListener( 'blur', checkFocus ); | ||
}; | ||
}, [] ); | ||
|
||
const __html = useMemo( () => { | ||
const doc = new window.DOMParser().parseFromString( html, 'text/html' ); | ||
const iframe = doc.querySelector( 'iframe' ); | ||
|
||
if ( iframe ) { | ||
iframe.removeAttribute( 'style' ); | ||
} | ||
|
||
const blockQuote = doc.querySelector( 'blockquote' ); | ||
|
||
if ( blockQuote ) { | ||
blockQuote.style.display = 'none'; | ||
} | ||
|
||
return doc.body.innerHTML; | ||
}, [ html ] ); | ||
|
||
return ( | ||
<div | ||
ref={ ref } | ||
className="wp-block-embed__wrapper" | ||
dangerouslySetInnerHTML={ { __html } } | ||
/> | ||
<div className="wp-block-embed__wrapper"> | ||
<iframe | ||
ref={ useMergeRefs( [ ref, useFocusableIframe() ] ) } | ||
title={ props.title } | ||
{ ...props } | ||
/> | ||
</div> | ||
); | ||
} |
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
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
This file was deleted.
Oops, something went wrong.
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
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
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,29 @@ | ||
# useFocusableIframe | ||
|
||
By default, it is not possible to detect when an iframe is focused or clicked | ||
within. This hook uses a technique which checks whether the target of a window | ||
`blur` event is the iframe, inferring that this has resulted in the focus of the | ||
iframe. It dispatches an emulated | ||
[`FocusEvent`](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent) on | ||
the iframe element with event bubbling, so a parent component binding its own | ||
`onFocus` event will account for focus transitioning within the iframe. | ||
|
||
## Usage | ||
|
||
Use with an `iframe`. You may pass `onFocus` directly as the callback to be | ||
invoked when the iframe receives focus, or on an ancestor component since the | ||
event will bubble. | ||
|
||
```jsx | ||
import { useFocusableIframe } from '@wordpress/compose'; | ||
|
||
const MyFocusableIframe = () => { | ||
return( | ||
<iframe | ||
ref={ useFocusableIframe() } | ||
src="/my-iframe-url" | ||
onFocus={ () => console.log( 'iframe is focused' ) } | ||
/> | ||
); | ||
}; | ||
``` |
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,34 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import useRefEffect from '../use-ref-effect'; | ||
|
||
/** | ||
* Dispatches a bubbling focus event when the iframe receives focus. Use | ||
* `onFocus` as usual on the iframe or a parent element. | ||
* | ||
* @return {Object} Ref to pass to the iframe. | ||
*/ | ||
export default function useFocusableIframe() { | ||
return useRefEffect( ( element ) => { | ||
const { ownerDocument } = element; | ||
if ( ! ownerDocument ) return; | ||
const { defaultView } = ownerDocument; | ||
if ( ! defaultView ) return; | ||
|
||
/** | ||
* Checks whether the iframe is the activeElement, inferring that it has | ||
* then received focus, and dispatches a focus event. | ||
*/ | ||
function checkFocus() { | ||
if ( ownerDocument && ownerDocument.activeElement === element ) { | ||
/** @type {HTMLElement} */ ( element ).focus(); | ||
} | ||
} | ||
|
||
defaultView.addEventListener( 'blur', checkFocus ); | ||
return () => { | ||
defaultView.removeEventListener( 'blur', checkFocus ); | ||
}; | ||
}, [] ); | ||
} |
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
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