この記事は最後に更新してから1年以上経過しています。

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

お勧め

has_category(2012年1月17日 更新)

bool has_category( [ mixed $category = '' [ , mixed $post = null ] ] )
投稿記事がカテゴリーに属しているか調べる。

comments_template(2018年5月27日 更新)

void comments_template( string $file = '/comments.php' , bool $separate_comments = false )
投稿ページ(post)または単一ページ(page)のコメント情報を取得し、コメント表示・投稿用のテンプレートファイルを読み込んで表示する。

is_taxonomy_hierarchical(2018年5月27日 更新)

bool is_taxonomy_hierarchical( string $taxonomy )
タクソノミーに階層(親子)関係があるか調べる。

send_confirmation_on_profile_email(2018年5月27日 更新)

void send_confirmation_on_profile_email()
メールアドレス変更を確認するメールを送信する。

get_the_modified_time(2023年9月20日 更新)

string | int | false get_the_modified_time( [ string $format = '' [ , int | WP_Post $post = null ] ] )
パラメータ$formatで指定したフォーマットで投稿の更新時刻を取得する。