Interactivity API を使ったタブブロックの作成

GitHub

公式ドキュメント

とりあえず create-block
をしようってコマンド叩いたけどイマイチファイル構成が気に食わなかった
npx @wordpress/create-block@latest --wp-env --wp-scripts

view.js をビルドしたいから、 build
コマンドと start
コマンドに--experimental-modules
を入れる

block.json のsupports
に "interactivity": true
を追加する
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "create-block/chiilog-iapi-tabs",
"version": "0.1.0",
"title": "IAPI Tabs",
"category": "widgets",
"icon": "smiley",
"description": "Example block scaffolded with Create Block tool.",
"example": {},
"supports": {
"html": false,
"interactivity": true
},
"textdomain": "chiilog-iapi-tabs",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScript": "file:./view.js"
}

- タブのラッパー
- ナビ
- パネル
で3つブロックいるね?

prettierいれたいから以下実行
npm i -w wp-content/plugins/chiilog-iapi-tabs -D @wordpress/prettier-config prettier@npm:wp-prettier@latest

ブロックのCSSはtailwind使う

render.php の get_block_wrapper_attributes
について

クラス追加したかったらこんな感じ
<?php
/**
* @param array $attributes The block attributes.
* @param string $content The block default content.
* @param WP_Block $block The block instance.
*
* @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
*/
$wrapper_attributes = get_block_wrapper_attributes(
[
'class' => 'custom-class',
]
);
?>
<div <?php echo $wrapper_attributes; ?>>
Block!
</div>

とりあえず下地になるタブのHTML+CSSは完成
<?php
/**
* @param array $attributes The block attributes.
* @param string $content The block default content.
* @param WP_Block $block The block instance.
*
* @see https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/block-api/block-metadata.md#render
*/
?>
<div
<?php echo get_block_wrapper_attributes(); ?>
data-wp-interactive="iapiTabs"
>
<div class="tab-nav" role="tablist">
<button
role="tab"
class="tab-nav__button"
data-wp-on--click="actions.selectTab"
aria-selected="true"
id="tab-1"
aria-controls="panel-1"
tabindex="0"
>
ナビ1
</button>
<button
role="tab"
class="tab-nav__button"
data-wp-on--click="actions.selectTab"
aria-selected="false"
id="tab-2"
aria-controls="panel-2"
tabindex="-1"
>
ナビ2
</button>
<button
role="tab"
class="tab-nav__button"
data-wp-on--click="actions.selectTab"
aria-selected="false"
id="tab-3"
aria-controls="panel-3"
tabindex="-1"
>
ナビ3
</button>
</div>
<div
id="panel-1"
role="tabpanel"
tabindex="0"
aria-labelledby="tab-1"
class="tab-panel"
aria-expanded="true"
aria-hidden="false"
>
パネル1
</div>
<div
id="panel-2"
role="tabpanel"
tabindex="0"
aria-labelledby="tab-2"
class="tab-panel"
aria-expanded="false"
aria-hidden="true"
>
パネル2
</div>
<div
id="panel-3"
role="tabpanel"
tabindex="0"
aria-labelledby="tab-3"
class="tab-panel"
aria-expanded="false"
aria-hidden="true"
>
パネル3
</div>
</div>

ここから
- コンテナ
- タブのボタン
- タブのパネル
をそれぞれ別のブロックに分解していく

いやでも分解たらだめじゃね?ボタンとパネル連動してるからこのまま1つのブロックとして扱う方がよさそう

- storeを設定するコンテナ
- ボタン&パネル
でいいのか

なるほど、query使えばできそうな感じ

innerBlockみたいに追加するボタンがないから別途ボタンをつける

む…。値が保存されないな…。そもそもひとつのdivの中でやるものじゃないからstringでjsonとして扱う方がいいのかなあ

{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "chiilog-blocks/iapi-tabs",
"version": "0.1.0",
"title": "IAPI Tabs",
"category": "widgets",
"icon": "smiley",
"description": "Example block scaffolded with Create Block tool.",
"attributes": {
"contents": {
"type": "array",
"default": []
},
"tabNavText": {
"type": "string",
"source": "text",
"selector": ".wp-block-chiilog-blocks-iapi-tabs__button span"
}
},
"supports": {
"interactivity": true
},
"textdomain": "chiilog-iapi-tabs",
"editorScript": "file:./index.js",
"editorStyle": "file:./index.css",
"style": "file:./style-index.css",
"viewScriptModule": "file:./view.js",
"render": "file:./render.php"
}
できた。array
のところに入れてた selector
は不要

ナビボタンまわりの追加
import type { BlockEditProps } from '@wordpress/blocks';
import { RichText, useBlockProps } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
type BlockAttributes = {
contents: TabItem[];
};
type TabItem = {
tabNavText: string;
};
const defaultContents: TabItem = {
tabNavText: '',
};
export default function Edit( {
attributes: { contents },
setAttributes,
}: BlockEditProps< BlockAttributes > ) {
return (
<div { ...useBlockProps() }>
<div className="mb-2">
<Button
variant="secondary"
onClick={ () => {
setAttributes( {
contents: [ ...contents, defaultContents ],
} );
} }
>
{ __( 'Add Tab', 'chiilog-iapi-tabs' ) }
</Button>
</div>
<div
className="wp-block-chiilog-blocks-iapi-tabs__nav"
role="tablist"
>
{ contents.map( ( tabItem, index ) => {
const tabId = index + 1;
const isLastTab = index === contents.length - 1;
return (
<button
role="tab"
className="wp-block-chiilog-blocks-iapi-tabs__button"
aria-selected={ isLastTab ? 'true' : 'false' }
id={ `tab-${ tabId }` }
aria-controls={ `panel-${ tabId }` }
tabIndex={ isLastTab ? 0 : -1 }
key={ index }
>
<RichText
value={ tabItem.tabNavText }
tagName="span"
onChange={ ( value ) => {
const newContents = [ ...contents ];
newContents[ index ] = {
...contents[ index ],
tabNavText: value,
};
setAttributes( {
contents: newContents,
} );
} }
placeholder={ __( 'Tab', 'chiilog-iapi-tabs' ) }
/>
</button>
);
} ) }
</div>
...
);
}

次はナビをクリックしたらカレントが変わるようにしたいから button
の onClick
をやっていく

useState
使ってカレントの位置をかえるようにする。これならAdd Tab
ボタン押したら追加したタブに移動してくれる。
import { useState } from '@wordpress/element';
import type { BlockEditProps } from '@wordpress/blocks';
import { RichText, useBlockProps } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
type BlockAttributes = {
contents: TabItem[];
};
type TabItem = {
tabNavText: string;
};
const defaultContents: TabItem = {
tabNavText: '',
};
export default function Edit( {
attributes: { contents },
setAttributes,
}: BlockEditProps< BlockAttributes > ) {
// 選択中のタブが何番目かを保持するステート
const [ currentTab, setCurrentTab ] = useState( 0 );
return (
<div { ...useBlockProps() }>
<div className="mb-2">
<Button
variant="secondary"
onClick={ () => {
setAttributes( {
contents: [ ...contents, defaultContents ],
} );
setCurrentTab( contents.length );
} }
>
{ __( 'Add Tab', 'chiilog-iapi-tabs' ) }
</Button>
</div>
<div
className="wp-block-chiilog-blocks-iapi-tabs__nav"
role="tablist"
>
{ contents.map( ( tabItem, index ) => {
const tabId = index + 1;
return (
<button
role="tab"
className="wp-block-chiilog-blocks-iapi-tabs__button"
aria-selected={
currentTab === index ? 'true' : 'false'
}
id={ `tab-${ tabId }` }
aria-controls={ `panel-${ tabId }` }
tabIndex={ currentTab === index ? 0 : -1 }
key={ index }
onClick={ () => {
setCurrentTab( index );
} }
>
<RichText
value={ tabItem.tabNavText }
tagName="span"
onChange={ ( value ) => {
const newContents = [ ...contents ];
newContents[ index ] = {
...contents[ index ],
tabNavText: value,
};
setAttributes( {
contents: newContents,
} );
} }
placeholder={ __( 'Tab', 'chiilog-iapi-tabs' ) }
/>
</button>
);
} ) }
</div>
...
);
}

増やすボタンがあれば消すボタンも必要

消すボタンは配置するならタブナビのボタンの横かなあ

タブがカレントのときだけ表示させるようにする
import { useState } from '@wordpress/element';
import type { BlockEditProps } from '@wordpress/blocks';
import { RichText, useBlockProps } from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { close } from '@wordpress/icons';
type BlockAttributes = {
contents: TabItem[];
};
type TabItem = {
tabNavText: string;
};
const defaultContents: TabItem = {
tabNavText: '',
};
export default function Edit( {
attributes: { contents },
setAttributes,
}: BlockEditProps< BlockAttributes > ) {
// 選択中のタブが何番目かを保持するステート
const [ currentTab, setCurrentTab ] = useState( 0 );
return (
<div { ...useBlockProps() }>
<div className="mb-2">
<Button
variant="secondary"
onClick={ () => {
setAttributes( {
contents: [ ...contents, defaultContents ],
} );
setCurrentTab( contents.length );
} }
>
{ __( 'Add Tab', 'chiilog-iapi-tabs' ) }
</Button>
</div>
<div
className="wp-block-chiilog-blocks-iapi-tabs__nav"
role="tablist"
>
{ contents.map( ( tabItem, index ) => {
const tabId = index + 1;
return (
<div className="flex flex-col gap-1 flex-auto">
{ currentTab === index ? (
<div>
<Button
size="small"
icon={ close }
label="削除"
onClick={ () => {
const newContents = [ ...contents ];
newContents.splice( index, 1 );
setAttributes( {
contents: newContents,
} );
} }
/>
</div>
) : null }
<button
role="tab"
className="wp-block-chiilog-blocks-iapi-tabs__button mt-auto"
aria-selected={
currentTab === index ? 'true' : 'false'
}
id={ `tab-${ tabId }` }
aria-controls={ `panel-${ tabId }` }
tabIndex={ currentTab === index ? 0 : -1 }
key={ index }
onClick={ () => {
setCurrentTab( index );
} }
>
<RichText
value={ tabItem.tabNavText }
tagName="span"
onChange={ ( value ) => {
const newContents = [ ...contents ];
newContents[ index ] = {
...contents[ index ],
tabNavText: value,
};
setAttributes( {
contents: newContents,
} );
} }
placeholder={ __(
'Tab',
'chiilog-iapi-tabs'
) }
/>
</button>
</div>
);
} ) }
</div>
...
</div>
);
}

左右に移動させたりも実装してみたいけどとりあえずそれは後回し

ナビ部分はできたので次はパネル。インナーブロックを複数置くことはできないので、やはりここはパネル用のブロックが必要そう。

わけた
/chiilog-iapi-tabs
├── container
│ ├── block.json
│ ├── edit.tsx
│ ├── index.js
│ ├── render.php
│ └── view.js
├── panel
│ ├── block.json
│ ├── edit.tsx
│ ├── index.js
│ └── save.tsx
└── style.css

パネル単体で増やせないように renderAppender: false
をいれておく

Add Tab押したときにパネルも一緒に追加する

createBlock
はあくまで挿入したいブロックをつくるやつであって、ブロックを挿入まではやらない。挿入まわりは core/block-editor
内の insertBlock
をつかう。

insertBlock
を使うにはuseDispatch
をインポートする。

import { useDispatch } from '@wordpress/data';
import { createBlock } from '@wordpress/blocks';
import { Button } from '@wordpress/components';
function InsertBlockButton() {
const { insertBlocks } = useDispatch('core/block-editor');
const insertParagraphBlock = () => {
// パラグラフブロックのインスタンスを作成
const block = createBlock('core/paragraph', {
content: 'Hello, world!',
});
// ブロックをエディターに挿入
insertBlocks(block);
};
return <Button onClick={insertParagraphBlock}>Insert Paragraph</Button>;
}

insertBlock
と insertBlocks
がある……

insertBlocks
がうまく動かんなと思ったけど、 clientId
を足したら動いた。

ChatGPTにコード提示してここうまく動かないんですけど…どうしたらいいっすかね……って聞いたらしっかり答えてくれるから助かる〜〜〜〜〜〜〜なに調べたらいいかとかの目安になる

(いろいろ省略)
ご質問のコードについて
ご質問のコードでは、insertBlocks
を使用してインナーブロックにブロックを挿入しようとしていますが、insertBlocks
はそのままでは親ブロックの内部にブロックを挿入するために直接使用することはできません。代わりに、親ブロックのclientId
を使用してreplaceInnerBlocks
、insertBlock
、またはinsertBlocks
(適切な引数とともに)を使用する必要があります。
さらに、useInnerBlocksProps
フックとともにインナーブロックを操作する際には、これらのブロック操作関数を適切に利用して目的のインナーブロック操作を行うようにしなければなりません。
っておしえてくれた

パネルを追加するまではできたけど、aria-xxx
系統をクリックのたびに変えたい 🤔

ブロックの attributes
を更新する方法はあるからこれを使えばよさそう

const updatePanelsVisibility = ( selectedIndex: number ) => {
blocks.forEach( ( block: BlockInstance, index: number ) => {
if ( block.name === panelBlockName ) {
const isVisible = index === selectedIndex;
updateBlockAttributes( block.clientId, {
ariaExpanded: isVisible,
ariaHidden: ! isVisible,
} );
}
} );
};
2回目以降のAdd Tabクリックでblocksの数が合わない。

useEffect
の出番なんだろうか……

useEffect( () => {
if ( blocks.length > 0 ) {
updatePanelsVisibility( currentTab );
}
}, [ blocks ] );
これ追加でうまく切り替えれた!

import { useDispatch, useSelect } from '@wordpress/data';
import { useEffect, useState } from '@wordpress/element';
import {
type BlockEditProps,
type BlockInstance,
createBlock,
} from '@wordpress/blocks';
import {
RichText,
useBlockProps,
useInnerBlocksProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { close } from '@wordpress/icons';
type BlockAttributes = {
contents: TabItem[];
};
type TabItem = {
tabNavText: string;
};
const defaultContents: TabItem = {
tabNavText: '',
};
const panelBlockName: string = 'chiilog-blocks/iapi-tabs-panel';
export default function Edit( {
attributes: { contents },
setAttributes,
clientId,
}: BlockEditProps< BlockAttributes > ) {
const { insertBlocks, updateBlockAttributes } =
useDispatch( 'core/block-editor' );
// 選択中のタブが何番目かを保持するステート
const [ currentTab, setCurrentTab ] = useState( 0 );
// インナーブロックの設定まわり
const ALLOWED_BLOCKS = [ panelBlockName ];
const innerBlocksProps = useInnerBlocksProps(
{},
{
allowedBlocks: ALLOWED_BLOCKS,
// @ts-ignore
renderAppender: false,
}
);
const blocks: BlockInstance[] = useSelect(
// @ts-ignore
( select ) => select( blockEditorStore ).getBlocks( clientId ),
[ clientId ]
);
useEffect( () => {
if ( blocks.length > 0 ) {
updatePanelsVisibility( currentTab );
}
}, [ blocks ] );
/**
* タブのナビアイテムを追加する
*/
const addNavItem = () => {
setAttributes( {
contents: [ ...contents, defaultContents ],
} );
};
/**
* タブのナビアイテムを削除する
*/
const removeNavItem = ( index: number ) => {
const newContents = [ ...contents ];
newContents.splice( index, 1 );
setAttributes( {
contents: newContents,
} );
};
/**
* タブのパネルを追加する
*/
const addNavPanel = ( index: number ) => {
const createPanel = createBlock(
panelBlockName,
{
panelId: `panel-${ index + 1 }`,
ariaLabelledby: `tab-${ index + 1 }`,
ariaExpanded: index === currentTab,
ariaHidden: index !== currentTab,
},
[
createBlock( 'core/paragraph', {
content: __(
`Tab Content : ${ index }`,
'chiilog-iapi-tabs'
),
} ),
]
);
insertBlocks( createPanel, index, clientId );
};
/**
* タブのパネルのカレントを更新する
*/
const updatePanelsVisibility = ( selectedIndex: number ) => {
blocks.forEach( ( block: BlockInstance, index: number ) => {
if ( block.name === panelBlockName ) {
const isVisible = index === selectedIndex;
updateBlockAttributes( block.clientId, {
ariaExpanded: isVisible,
ariaHidden: ! isVisible,
} );
}
console.log( blocks );
console.log( `selectedIndex: ${ selectedIndex }` );
console.log( `index: ${ index }` );
} );
};
return (
<div { ...useBlockProps() }>
<div className="mb-2">
<Button
variant="secondary"
onClick={ () => {
addNavItem();
setCurrentTab( contents.length );
addNavPanel( contents.length );
updatePanelsVisibility( contents.length );
} }
>
{ __( 'Add Tab', 'chiilog-iapi-tabs' ) }
</Button>
</div>
<div
className="wp-block-chiilog-blocks-iapi-tabs__nav"
role="tablist"
>
{ contents.map( ( tabItem, index ) => {
const tabId = index + 1;
return (
<div
key={ `nav-${ index }` }
className="flex flex-col gap-1 flex-auto"
>
{ currentTab === index ? (
<>
<Button
size="small"
icon={ close }
label="削除"
onClick={ () => {
removeNavItem( index );
setCurrentTab( 0 );
} }
/>
</>
) : null }
<button
role="tab"
className="wp-block-chiilog-blocks-iapi-tabs__button mt-auto"
aria-selected={
currentTab === index ? 'true' : 'false'
}
id={ `tab-${ tabId }` }
aria-controls={ `panel-${ tabId }` }
tabIndex={ currentTab === index ? 0 : -1 }
onClick={ () => {
setCurrentTab( index );
updatePanelsVisibility( index );
} }
>
<RichText
value={ tabItem.tabNavText }
tagName="span"
onChange={ ( value ) => {
const newContents = [ ...contents ];
newContents[ index ] = {
...contents[ index ],
tabNavText: value,
};
setAttributes( {
contents: newContents,
} );
} }
placeholder={ __(
'Tab',
'chiilog-iapi-tabs'
) }
/>
</button>
</div>
);
} ) }
</div>
<div { ...innerBlocksProps } />
</div>
);
}

edit.tsx完成〜〜〜〜〜〜〜〜〜〜!!
実質タブのJSまわりにかかったの1日かな…?
import { useDispatch, useSelect } from '@wordpress/data';
import { useEffect, useState } from '@wordpress/element';
import {
type BlockEditProps,
type BlockInstance,
createBlock,
} from '@wordpress/blocks';
import {
RichText,
useBlockProps,
useInnerBlocksProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { close } from '@wordpress/icons';
type BlockAttributes = {
contents: TabItem[];
};
type TabItem = {
tabNavText: string;
};
const defaultContents: TabItem = {
tabNavText: '',
};
const panelBlockName: string = 'chiilog-blocks/iapi-tabs-panel';
export default function Edit( {
attributes: { contents },
setAttributes,
clientId,
}: BlockEditProps< BlockAttributes > ) {
const { insertBlocks, removeBlocks, updateBlockAttributes } =
useDispatch( 'core/block-editor' );
// 選択中のタブが何番目かを保持するステート
const [ currentTab, setCurrentTab ] = useState( 0 );
// インナーブロックの設定まわり
const ALLOWED_BLOCKS = [ panelBlockName ];
const innerBlocksProps = useInnerBlocksProps(
{},
{
allowedBlocks: ALLOWED_BLOCKS,
// @ts-ignore
renderAppender: false,
}
);
const blocks: BlockInstance[] = useSelect(
// @ts-ignore
( select ) => select( blockEditorStore ).getBlocks( clientId ),
[ clientId ]
);
/**
* 2回目以降のblocks追加でうまくblocksが取得できないため、
* blocksが更新されたタイミングでパネルの表示を更新する
*/
useEffect( () => {
if ( blocks.length > 0 ) {
updatePanelsVisibility( currentTab );
}
}, [ blocks ] );
/**
* タブのナビアイテムを追加する
*/
const addNavItem = () => {
setAttributes( {
contents: [ ...contents, defaultContents ],
} );
};
/**
* タブのナビアイテムを削除する
*/
const removeNavItem = ( index: number ) => {
const newContents = [ ...contents ];
newContents.splice( index, 1 );
setAttributes( {
contents: newContents,
} );
};
/**
* タブのパネルを追加する
*/
const addNavPanel = ( index: number ) => {
const createPanel = createBlock(
panelBlockName,
{
panelId: `panel-${ index + 1 }`,
ariaLabelledby: `tab-${ index + 1 }`,
ariaExpanded: index === currentTab,
ariaHidden: index !== currentTab,
},
[
createBlock( 'core/paragraph', {
content: __(
`Tab Content : ${ index }`,
'chiilog-iapi-tabs'
),
} ),
]
);
insertBlocks( createPanel, index, clientId );
};
/**
* タブのパネルを削除する
*/
const removeNavPanel = ( index: number ) => {
removeBlocks( blocks[ index ].clientId, false );
};
/**
* タブのパネルのカレントを更新する
*/
const updatePanelsVisibility = ( selectedIndex: number ) => {
blocks.forEach( ( block: BlockInstance, index: number ) => {
if ( block.name === panelBlockName ) {
const isVisible = index === selectedIndex;
updateBlockAttributes( block.clientId, {
ariaExpanded: isVisible,
ariaHidden: ! isVisible,
} );
}
} );
};
return (
<div { ...useBlockProps() }>
<div className="mb-2">
<Button
variant="secondary"
onClick={ () => {
addNavItem();
setCurrentTab( contents.length );
addNavPanel( contents.length );
updatePanelsVisibility( contents.length );
} }
>
{ __( 'Add Tab', 'chiilog-iapi-tabs' ) }
</Button>
</div>
<div
className="wp-block-chiilog-blocks-iapi-tabs__nav"
role="tablist"
>
{ contents.map( ( tabItem, index ) => {
const tabId = index + 1;
return (
<div
key={ `nav-${ index }` }
className="flex flex-col gap-1 flex-auto"
>
{ currentTab === index ? (
<>
<Button
size="small"
icon={ close }
label={ __(
'Remove Tab',
'chiilog-iapi-tabs'
) }
onClick={ () => {
removeNavItem( index );
setCurrentTab( 0 );
removeNavPanel( index );
updatePanelsVisibility( 0 );
} }
/>
</>
) : null }
<button
role="tab"
className="wp-block-chiilog-blocks-iapi-tabs__button mt-auto"
aria-selected={
currentTab === index ? 'true' : 'false'
}
id={ `tab-${ tabId }` }
aria-controls={ `panel-${ tabId }` }
tabIndex={ currentTab === index ? 0 : -1 }
onClick={ () => {
setCurrentTab( index );
updatePanelsVisibility( index );
} }
>
<RichText
value={ tabItem.tabNavText }
tagName="span"
onChange={ ( value ) => {
const newContents = [ ...contents ];
newContents[ index ] = {
...contents[ index ],
tabNavText: value,
};
setAttributes( {
contents: newContents,
} );
} }
placeholder={ __(
'Tab',
'chiilog-iapi-tabs'
) }
/>
</button>
</div>
);
} ) }
</div>
<div { ...innerBlocksProps } />
</div>
);
}

パネルのブロックインナーブロックだけどIAPIできるのかなあって試しに save.tsx で data-wp-on--click
つけたら動いたからいけそう
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
import type { BlockEditProps } from '@wordpress/blocks';
import type { BlockAttributes } from './type';
export default function Edit( {
attributes: { panelId, ariaLabelledby, ariaExpanded, ariaHidden },
}: BlockEditProps< BlockAttributes > ) {
return (
<div
{ ...useBlockProps.save( { anchor: panelId } ) }
role="tabpanel"
tabIndex={ 0 }
aria-labelledby={ ariaLabelledby }
aria-expanded={ ariaExpanded }
aria-hidden={ ariaHidden }
data-wp-on--click="actions.selectTab"
>
<div { ...useInnerBlocksProps.save() } />
</div>
);
}

まずはrender.php でナビ、パネルともに最初のやつをカレントにする

うーん、 data-wp-bind--xxx
いれたらエディターでWarning出るな…
Warning: Trying to access array offset on value of type bool in /var/www/html/wp-includes/interactivity-api/class-wp-interactivity-api.php on line 385
Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/wp-includes/interactivity-api/class-wp-interactivity-api.php:385) in /var/www/html/wp-admin/admin-header.php on line 9

IAPI系の記述消したら消えたから、これは WP_HTML_Tag_Processor
使って後付したほうがいいやつかなあ


記述参考

add_filter
は render_block_
はじまり。今回私が作ってるやつで言うと
add_filter( 'render_block_chiilog-blocks/iapi-tabs-panel', 'add_directives_to_inner_blocks', 10, 2 );
こうなる

めちょこまかい

function add_directives_to_inner_blocks( $block_content, $block ) {
$panels = new WP_HTML_Tag_Processor( $block_content );
if ( $panels->next_tag( array( 'class_name' => 'wp-block-chiilog-blocks-iapi-tabs-panel' ) ) ) {
$panels->set_attribute( 'data-wp-bind--aria-expanded', 'false' );
$panels->set_attribute( 'data-wp-bind--aria-hidden', 'true' );
}
return $panels->get_updated_html();
}
add_filter( 'render_block_chiilog-blocks/iapi-tabs-panel', 'add_directives_to_inner_blocks', 10, 2 );
とりあえず一旦ベタ打ちでいれる

最初のパネルだけ表示させたいのでwhileつかう。あとadd_filterの対象を変える。
function add_directives_to_inner_blocks( $block_content, $block ) {
$panels = new WP_HTML_Tag_Processor( $block_content );
$panelCount = 0;
while ( $panels->next_tag() ) {
foreach ( $panels->class_list() as $class_name ) {
if ( $class_name === 'wp-block-chiilog-blocks-iapi-tabs-panel' ) {
$panels->set_attribute( 'data-wp-bind--aria-expanded', $panelCount === 0);
$panels->set_attribute( 'data-wp-bind--aria-hidden', $panelCount !== 0 );
$panelCount++;
}
}
}
return $panels->get_updated_html();
}
add_filter( 'render_block_chiilog-blocks/iapi-tabs', 'add_directives_to_inner_blocks', 10, 2 );

これで一旦は最初のナビアイテムとパネルを表示させるまではできたぞ!

クリックしたらカレントを変える。ついにきた

とりあえず wp_interactivity_state
で初期値いれておけばいいのかな

renderに書けばいいって書いてあるけど動かぬ…

state
を console.log してみたら入ってたから、 wp_interactivity_state
で state
追加したら getContext()
はいらないっぽいな

The state defined on the server with wp_interactivity_state() gets merged with the stores defined in the view.js files.
マージ済みだからとくになんか作業いらないってことか? 🤔

とりあえずこんな感じに。
/**
* WordPress dependencies
*/
import { store } from '@wordpress/interactivity';
const { state, actions } = store( 'iapiTabs', {
state: {
get currentItem() {
if ( state.currentTab ) {
return state.currentTab;
}
return 0;
},
},
actions: {
selectTab: () => {
console.log( state.currentItem );
},
},
callbacks: {},
} );
wp_interactivity_state( 'iapiTabs', array (
'currentTab' => 1
));

さて………どうやってナビ動かすんだ? 🤔

/**
* パネルの aria-expanded と aria-hidden を data-wp-bind--xxx に変換する
*
* @param $block_content
* @param $block
* @return string
*/
function add_directives_to_inner_blocks( $block_content, $block ) {
$panels = new WP_HTML_Tag_Processor( $block_content );
$panelCount = 0;
while ( $panels->next_tag() ) {
foreach ( $panels->class_list() as $class_name ) {
if ( $class_name === 'wp-block-chiilog-blocks-iapi-tabs-panel' ) {
$panels->set_attribute( 'data-wp-bind--aria-expanded', 'state.panelExpanded' );
$panels->set_attribute( 'data-wp-bind--aria-hidden', 'state.panelHidden' );
$panels->set_attribute( 'data-wp-context', '{ "panelPosition": ' . $panelCount . ' }' );
$panelCount++;
}
}
}
return $panels->get_updated_html();
}
add_filter( 'render_block_chiilog-blocks/iapi-tabs', 'add_directives_to_inner_blocks', 10, 2 );
stateに差し替え

view.js でカレントつける
/**
* WordPress dependencies
*/
import { getContext, store } from '@wordpress/interactivity';
const { state, actions } = store( 'iapiTabs', {
state: {
get panelExpanded() {
const ctx = getContext();
return ctx.panelPosition === state.currentItem;
},
get panelHidden() {
const ctx = getContext();
return ctx.panelPosition !== state.currentItem;
},
},
actions: {
selectTab: () => {
console.log( 'click' );
},
},
callbacks: {},
} );

data-wp-context
使って番号もたせて、番号とカレント位置が同じだったら表示的なロジックになるかなあ

ナビボタン
<button
role="tab"
class="wp-block-chiilog-blocks-iapi-tabs__button"
data-wp-on--click="actions.changeCurrentTab"
data-wp-bind--aria-selected="state.tabSelected"
id="tab-<?php echo esc_attr( $tabNumber ); ?>"
aria-controls="panel-<?php echo esc_attr( $tabNumber ); ?>"
tabindex="<?php echo esc_attr( $index === 0 ? '0' : '-1' ); ?>"
data-wp-context='{ "position": <?php echo esc_attr( $index ); ?> }'
>
<?php echo esc_html( $navItem['tabNavText'] ); ?>
</button>
パネル部分
$panels->set_attribute( 'data-wp-bind--aria-expanded', 'state.panelExpanded' );
$panels->set_attribute( 'data-wp-bind--aria-hidden', 'state.panelHidden' );
$panels->set_attribute( 'data-wp-context', '{ "position": ' . $panelCount . ' }' );
$panelCount++;
view.js
/**
* WordPress dependencies
*/
import { getContext, store } from '@wordpress/interactivity';
const { state, actions } = store( 'iapiTabs', {
state: {
get panelExpanded() {
const ctx = getContext();
return ctx.position === state.currentTab;
},
get panelHidden() {
const ctx = getContext();
return ctx.position !== state.currentTab;
},
get tabSelected() {
const ctx = getContext();
return ctx.position === state.currentTab;
},
},
actions: {
changeCurrentTab: () => {
const ctx = getContext();
state.currentTab = ctx.position;
},
},
callbacks: {},
} );
できた!

タブ実装にあたりかなり参考にさせていただきました!!!

同じ投稿内で複数使うと一緒に動いちゃう

ていうかこれsave.jsで良かった感もあるな……

あー…store名にユニークな値をもたせたら解決するのか?

wp_interactivity_state
をやめて、wp_interactivity_data_wp_context
で初期値をもたせたら解決した!

それはそれとして複数配置すると id が重複してくるので、ブロックの clientIdを使って重複しないようにする。

複数使わないときは wp_interactivity_state
で、複数使うときは wp_interactivity_data_wp_context
使うって使い分けになるのかなあ。

ああ、save.js でもいいじゃんと思ったけど今はrender.phpの方しかサポートしてないのね

ほほう、wp_unique_id便利そう