Popoverコンポーネントのanchorプロパティについて考えたときのメモ

説明

カスタムブロックやコアブロック向けのプラグインでお世話になるPopoverコンポーネント。表示位置を決定する上で大きく影響するのがanchorプロパティである。フィルターのコールバック関数で対象ブロックに干渉できない場合はどうしたらよいのだろうか。

refプロパティを使える場合

Popoverコンポーネントを使い方は「Popover」が参考になる。その表示位置を指定する際に重要なのがanchorプロパティで、そこには対象のコンポーネントのrefプロパティを使用することが多い。たとえば、ToolbarButtonコンポーネントの下にPopoverコンポーネントを使って何かを表示する場合は次のようなコードになる。

const [ popoverAnchor, setPopoverAnchor ] = useState();
const [ visiblePopover, setVisiblePopover ] = useState( false );

return (
<BlockControls>
	<ToolbarGroup>
		<ToolbarButton
			icon={ "admin-comments" }
			onClick={ ( event ) => { setVisiblePopover( true ); } }
			ref={ setPopoverAnchor }
		/>
	</ToolbarGroup>
</BlockControls>
{ visiblePopover && (
	<Popover
		anchor={ popoverAnchor }
		placement={ "bottom" }
	>
		Popover.
	</Popover>
) }
);

この場合、PopoverコンポーネントのanchorプロパティにはToolbarButtonコンポーネントのrefプロパティに指定したsetPopoverAnchorによって更新されるpopoverAnchorを指定している。コンソールに出力されたpopoverAnchorの内容は次の通りで、button要素となる(一部省略)。

<button type="button" id=":r1r:" data-toolbar-item="true" class="components-button components-toolbar-button has-icon" tabindex="-1">...</button>

refプロパティが使えない場合

上記をふまえて今回困ったのが、editor.BlockEditフィルターのコールバック関数内でボタンをクリックし、編集対象のブロックの右側にPopoverコンポーネントを使って各種設定コンポーネントを配置したいケースである。'editor.BlockEdit'フィルターは次のコードになるが、ここには編集対象のブロックは登場せず、refプロパティを指定することができない。

const myPluginEffectControls = createHigherOrderComponent( ( BlockEdit ) => {

// 省略

	return ( props ) => {
		return (
			<>
				<BlockEdit { ...props } />

// 省略

			</>
		);
	};
}, 'myPluginEffectControls' );

addFilter( 
	'editor.BlockEdit',
	'my-plugin/my-effect',
	myPluginEffectControls
);

よい解決作がないか「ChatGPT」に問い合わせてみたところ、ブロックのクライアントIDを使って要素を取得する方法が提示された。クライアントIDはeditor.BlockEditフィルターのコールバック関数のpropsに含まれていたので、ここではそれを使用する。対象ブロックの要素を見てみると、id属性は'block-'に続いてクライアントIDがあり、data-block属性にもクライアントIDがあった。どちらでも検索できそうなので、ここではid属性を使って要素を取得してみた。

const {
	name,
	attributes,
	setAttributes,
	clientId
} = props;

const [ popoverAnchor, setPopoverAnchor ] = useState( null );
useEffect( () => {
	const element = document.getElementById( `block-${clientId}` );
	console.log( element );
	setPopoverAnchor( element );
}, [ clientId ] );

これでコンソールに出力されたelementの内容は「null」。エディター画面のHTMLソースを見直すと、ブロックエディターの部分がiframe要素になっており、それが原因で対象ブロックの要素が取得できなかったようだ。このことをふまえ、次のように修正した。

useEffect( () => {
	const element = document.querySelector( '[name="editor-canvas"]' ).contentWindow.document.getElementById( `block-${clientId}` );
	console.log( element );
	setPopoverAnchor( element );
}, [ clientId ] );

これでコンソールには対象ブロックの要素が出力されたことを確認できた(一部省略)。

<figure tabindex="0" class="block-editor-block-list__block wp-block is-selected drop-shadow is-resized size-large wp-elements-0 wp-duotone-0 wp-block-image is-hovered" id="block-4d2bb532-055b-4f13-9d84-82d5f279962c" role="document" aria-label="ブロック: 画像" data-block="4d2bb532-055b-4f13-9d84-82d5f279962c" data-type="core/image" data-title="画像">...</figure>

いくつかの環境で試したところ、ブロックエディターの部分がiframe要素になるケースとそうならないケースがあることがわかり、最終的には次のコードに落ち着いた。

useEffect( () => {
	const element = document.querySelector( '[name="editor-canvas"]' ) ?
		document.querySelector( '[name="editor-canvas"]' ).contentWindow.document.getElementById( `block-${clientId}` ) :
		document.getElementById( `block-${clientId}` );
	setPopoverAnchor( element );
}, [ clientId ] );

対象ブロックの要素は無事取得できるようになった。対象ブロックの要素を取得するAPIか、propsにref関連の情報が追加されれば、もっとスッキリする気がしるのだが、あまり需要がないとここままかもしれない。


最終更新 : 2024年08月19日 12:16

お勧め

wp_save_post_revision(2024年1月10日 更新)

int | WP_Error | void wp_save_post_revision( int $post_id )
現状の投稿のリビジョンを作成する。

get_the_author_posts_link(2019年8月21日 更新)

string get_the_author_posts_link( )
投稿者アーカイブページのリンクを取得する。

get_year_link(2012年2月2日 更新)

string get_year_link( mixed $year )
年アーカイブのURLを取得する。

register_taxonomy(2022年6月1日 更新)

WP_Taxonomy | WP_Error register_taxonomy( string $taxonomy, array | string $object_type, array | string $args = array() )
タクソノミーを登録する。

wp_add_object_terms(2013年8月6日 更新)

mixed wp_add_object_terms( int $object_id, mixed $terms, mixed $taxonomy )
投稿情報などにタクソノミーを追加する。