WordPress Logo WordPress 初めてのブロック開発

以下は WordPress 公式コース「ブロック開発入門: 最初のカスタムブロックを構築」を参考にカスタムブロックを作成する方法についてまとめたものです。

公式コースの内容とほぼ同じですが、追加でビルドして Zip ファイルを作成する手順や翻訳ファイルの作成手順も掲載しています。

参考にさせていただいた Build your first custom block は英文ですが、わかりやすい解説でとても良くできているのでおすすめです(受講するにはアカウント登録が必要ですが無料です)。

また、日本語の「チュートリアル: はじめてのブロック作成」もおすすめです。

更新日:2025年01月14日

作成日:2024年12月13日

前提条件

ブロックの開発では以下が必要になります。

  • ローカル環境( XAMPP や MAMP、Local by Flywheel、wp-env など)
  • コードエディター(VS Code や Sublime など)
  • ターミナル
  • Node.js

以下ではすでにローカル環境と Node がインストールされていることを前提にしています。

関連ページ:

最終的に作成されるブロックは以下のような雑誌や新聞記事のような段組みのレイアウトを構成するブロックで、列数や列幅、罫線の種類などをコントロールでカスタマイズすることができます。

以下で使用している Create Block のバージョンは 4.56.0、WordPress のバージョンは 6.7.1 です。

Create Block ツールの変更点

※ Create Block のバージョン 4.58.0 からは、ひな型の src 及び build ディレクトリ内にサブフォルダが作成され、その中にファイルが配置されるように変更されています。そのため、以下のフォルダ構成やプラグインファイル(ブロックの登録)のビルド先のパスなどは適宜読み替えてください。

関連ページ

Create Block ツール

Create Block は、ブロックの作成と登録に必要なひな形を作成する公式サポートツールです。

create-block コマンドを実行すると、ブロック開発に必要なすべてのファイルを生成してくれます。

参考ページ:

以下は create-block コマンドの書式です。

% npx @wordpress/create-block@latest [options] [slug]

slug

slug にはひな形ファイルの出力先 (フォルダ名) を指定します。--title オプションを省略した場合、指定した値はプラグイン名やブロックの表示タイトルとしても使用されます。

slug を省略すると、スクリプトは対話モードで実行され、ひな形プロジェクトに必要な入力 (slug、title、namespace…)を求めるプロンプトが表示されます。

options

options には以下のオプションを指定できます(省略した場合はデフォルト値が適用されます)。

category などの一部のオプションの設定は、後から変更することができます。

オプション 説明
-V, --version バージョン番号を出力。デフォルトは 0.1.0。
-t, --template <name> プロジェクトテンプレートタイプ名。指定可能な値: "static" (デフォルト)、"es5"、外部 npm パッケージ名、ローカルディレクトリへのパス。
--no-plugin ひな形ブロックファイルのみを作成します。
--namespace <value> ブロック名の内部名前空間。--namespace を省略した場合の名前空間の値は create-block になります。
--title <value> ブロックの表示タイトル及びプラグイン名。省略すると slug で指定した値が使用されて生成されます。
--short-description <value> ブロックと WordPress プラグインの短い説明。
--category <name> ブロックのカテゴリー名(text、media、design、widgets、theme、embed)。デフォルトは widgets。
--wp-scripts @wordpress/scripts パッケージとの統合を有効化。
--no-wp-scripts @wordpress/scripts パッケージとの統合を無効化。
--wp-env @wordpress/env パッケージとの統合を有効化。この引数を指定すると、create-block は wp-env をインストールしてくれるので、簡単にローカル環境を構築できます。
-h, --help ヘルプ(使用方法)の出力
--variant テンプレートで定義されたブロックのバリエーションを選択

対話モード

slug を指定しないでコマンドを実行すると対話モードになります。表示されるそれぞれの問いに対して値を入力して return キーを押すことでオプションを設定することができます。

何も値を入力せず return キーを押せば、表示されているデフォルト値が設定されます。

対話モードで「Do you want to customize the WordPress plugin? 」の質問に対して「Yes」を選択すると、プラグインファイルのヘッダーフィールドの設定項目を指定することができます。「No」を選択すると、対話モードは終了しコマンドが実行されます。

対話モードで設定できるオプション
オプション 説明 デフォルト
variant 動的ブロック(ダイナミックブロック)を生成する場合、dynamic を選択 static
slug ファイルの出力先ディレクトリ名に使用される文字列 example-static
namespace 名前空間。ブロックをユニークに識別できる文字列 create-block
title プラグインの名前(プラグインヘッダの Plugin Name)及びブロックの表示タイトル Example Static
description ブロックの短い説明を指定。プラグインヘッダの Description。 Example block scaffolded with Create Block tool.
dashicon ブロックのアイコン。 smiley
category カテゴリー。text、media、design、widgets、theme、embed から選択 widgets
plugin URL プラグインのホームページ URL。
version プラグインヘッダに記載されるプラグインのバージョン 0.1.0
plugin author プラグインヘッダに記載されるプラグインの作者。 The WordPress Contributors
plugin's license プラグインヘッダに記載されるプラグインのライセンス(short name) GPL-2.0-or-later
link to license プラグインヘッダに記載されるライセンスの全文へのリンク https://www.gnu.org/licenses/gpl-2.0.html
Domain Path 翻訳のドメインパス
Update URI カスタムアップデート URI

これらのオプションは雛形のファイルを生成後、変更することができます。

ブロックのひな形を作成

ブロックのひな形(初期構成)を作成するにはターミナルを開き、プロジェクト(ブロック)を作成するディレクトリに移動します。これは通常、WordPress インストールの wp-content/plugins(プラグインディレクトリ)になります。

% cd wp-content/plugins

目的のディレクトリに移動したら、次のコマンドを入力し、return キーを押して実行します。

% npx @wordpress/create-block@latest multi-columns

上記のコマンドは、npx @wordpress/create-block@latest [オプション] [スラッグ] という書式に従い、オプションを省略してスラッグに multi-columns を指定しています。

スラッグは、ひな形ファイルの出力先 (フォルダ名) 及びプラグインの名前として使用されます。

上記の場合、multi-columns というフォルダが作成されその中にファイルが出力され、--title オプションを指定していないので、プラグイン名とブロックの表示タイトルは Multi Columns になります。

以下は実行例です。コマンドのレスポンスには npm start などの利用可能なコマンドも表示されます。

上記コマンドを実行して、もし、以下のようなコマンドをアップデートする表示が出たら y を入力して return キーを押して更新をインストールします。

Need to install the following packages:
@wordpress/create-block@4.56.0
Ok to proceed? (y) // y と入力して return キーを押す

コマンドの実行により plugins ディレクトリの中に以下のような multi-columns というディレクトリが作成されます。

--wp-env オプション

create-block コマンドに --wp-env オプションを指定して実行すると、作成するプラグインディレクトリに wp-env がインストールされるので、コマンドラインから簡単にローカル環境を構築することができます。

そのため、事前にローカル環境を構築する必要がありません。但し、Docker がインストールされている必要があります(Docker の知識や設定は不要です)。

詳細:wp-env の使い方/ブロック開発の環境セットアップ

ブロックのファイル構成

作成されたブロックのひな形 multi-columns は以下のような構成になっています。

以下の3つのディレクトリが作成されています。

build ブロックの最終的なデプロイ可能なビルドが配置される場所。src ディレクトリのファイルがビルドされてここに出力されます。
node_modules ビルドプロセスが依存するすべてのモジュールが配置されている場所。開発環境を構築するのに必要なファイルが入っています。
src 開発で使用するファイルが格納されているディレクトリ。このフォルダのファイルを編集します(最終的に build ディレクトリにコンパイルされます)。

開発では src ディレクトリ内のファイルを操作(編集、追加、削除)します。通常は build と node_modules ディレクトリ内のファイルを操作することはありません。

※ create-block の version 4.58.0 からは、src 及び build ディレクトリの中にサブフォルダが作成され、その中にファイルが配置されるように変更されています。

.editorconfig と .gitignore

.editorconfig と .gitignore は、それぞれ開発環境とバージョン管理で使用されます。

プラグインファイル multi-columns.php

この例の場合、create-block コマンドの slug に multi-columns と指定したので、multi-columns.php という名前の PHP ファイル(プラグインファイル)が自動的に生成されます。

基本的にこのプロジェクト(作成するブロック)は WordPress プラグインです。

そしてこの multi-columns.php がプラグインの実行を開始する(WordPress にこれがプラグインであることを知らせる)メインのプラグインファイルになります。参考:Plugin Basics

また、このファイルでブロックをサーバーサイド(PHP サイド)に登録します。

ファイルの先頭部分には、以下のようなプラグインの情報を定義するコメントブロックがあります。

この例では slug に multi-columns を指定しただけなので、Plugin Name と Text Domain が自動的に設定されていますが、その他はデフォルトの値が設定されているので、必要に応じて変更します。

Description はプラグインページの説明部分に表示されます。

これらの情報は、create-block コマンドを実行する際に slug を指定せずに、対話モードで実行することで設定することもできます。

詳細:Header Requirements

<?php
/**
 * Plugin Name:       Multi Columns
 * Description:       Custom Multi Columns block for newspaper style.
 * Requires at least: 6.6
 * Requires PHP:      7.2
 * Version:           0.1.0
 * Author:            The WordPress Contributors
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       multi-columns
 *
 * @package CreateBlock
 */

プラグインヘッダーの後には以下のようなセキュリティ対策のコードが記述されています。

ABSPATH は WordPress がインストールされているディレクトリのフルパスが入っている定数です(wp-load.php と wp-config.php で定義されています)。

defined は指定した名前の定数が存在するかどうかを調べる関数です。もし、直接ファイルにアクセスされた場合は ABSPATH が定義されていないので、その場合は exit するようにしています。

if (! defined('ABSPATH')) {
  exit; // Exit if accessed directly.
}

セキュリティ対策のコードの後には以下のような init アクションにフックされる関数が記述されています。

この関数は register_block_type() 関数に build ディレクトリへのパス(ブロックを定義するメタデータを含む block.json の場所)をパラメーターとして渡して、block.json ファイルに保存されたメタデータを読み込むことで、ブロックを(サーバーサイドに)登録しています。

詳細:ブロックの登録

function create_block_multi_columns_block_init() {
  register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'create_block_multi_columns_block_init' );

package-lock.json

package-lock.json はインストールしたパッケージの依存パッケージが記載されている npm のファイルで、通常は何もする必要がないファイルです。

package.json

package.json はインストールされたパッケージに関する設定情報などが記述された npm のファイルです。

package.json の scripts フィールドには開発時やビルドの際に使用できるコマンド(npm scripts)が登録されています。

npm run に続けて scripts フィールドのキー名を指定して実行すると登録されているスクリプトが実行されます。例えば、build を指定して npm run build で npx wp-scripts build を実行できます。

{
  "name": "multi-columns",
  "version": "0.1.0",
  "description": "Example block scaffolded with Create Block tool.",
  "author": "The WordPress Contributors",
  "license": "GPL-2.0-or-later",
  "main": "build/index.js",
  "scripts": {
    "build": "wp-scripts build",
    "format": "wp-scripts format",
    "lint:css": "wp-scripts lint-style",
    "lint:js": "wp-scripts lint-js",
    "packages-update": "wp-scripts packages-update",
    "plugin-zip": "wp-scripts plugin-zip",
    "start": "wp-scripts start"
  },
  "devDependencies": {
    "@wordpress/scripts": "^30.6.0"
  }
}

readme.txt

readme.txt ファイルは、プラグインを WordPress プラグインディレクトリ公開する場合にのみ編集する必要があります。

src ディレクトリ

src ディレクトリには開発用のファイルが格納されています。ビルドを実行すると、このフォルダからファイルを取り出し、プロジェクトの build フォルダに本番用のファイルを生成します。

参考:ブロックのファイル構成(日本語ドキュメント)

以下は各ファイルの概要です(この時点で理解できなくても問題ありません)。

block.json

block.jsonファイルはブロックのメタデータを JSON オブジェクトとして構造化して保存します。

このファイルでは、edit.js と save.js で定義された関数がパラメーターとして使用できる属性(attributes)やブロックサポート(supports)などを定義します(初期状態では attributes は定義されていません)。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "widgets",
  "icon": "smiley",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": false
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

参考:block.json のメタデータ

edit.js

edit.js はブロックがどのように機能し、どのようにエディターに表示されるかを制御するファイルです。

エディター内でブロックがどのように表示され、機能するかを決定する React コンポーネント Edit() をエクスポートし、index.js ファイルの registerBlockType 関数の edit プロパティに渡されます。

このファイルでブロックのマークアップやエディターのインスペクターパネルに表示されるコントロールなどを定義します。

ここで定義される Edit() は通常、block.json で定義された属性(attributes)や setAttributes などのメソッドを含むパラメーター(オブジェクト)を受け取ります。

import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import './editor.scss';

// Edit 関数(コンポーネント)をエクスポート
export default function Edit() {
  return (
    <p { ...useBlockProps() }>
      { __( 'Multi Columns – hello from the editor!', 'multi-columns' ) }
    </p>
  );
}

editor.scss

editor.scss は、ブロックエディターでのブロックの外観をスタイルする CSS を含む Sass ファイルで、多くはブロックのユーザーインターフェイスに特化したスタイルに使用されます。

このファイルはビルド時に index.css に変換されます(index.css は block.json で editorStyle ファイルとして定義されています)。

.wp-block-create-block-multi-columns {
  border: 1px dotted #f00;
}

index.js

index.js はブロックの JavaScript 実行の開始点(エントリーポイント)です。

@wordpress/blocks パッケージの registerBlockType を使用して JavaScript でブロックのクライアントサイドの設定を登録します。※ block.json では index.js が editorScript プロパティの値として指定されています。

registerBlockType の第1引数には、インポートした block.json のメタデータから取得したブロック名(metadata.name)を指定し、第2引数にはブロックの設定オブジェクトを指定して実行します。

第2引数は edit と save という 2 つのプロパティを含むオブジェクトです。edit プロパティに edit.js からインポートした Edit コンポーネントを、save プロパティに save.js からインポートした save 関数を指定します。

import { registerBlockType } from '@wordpress/blocks';
import './style.scss';
import Edit from './edit';
import save from './save';
import metadata from './block.json';  // block.json のメタデータ

registerBlockType( metadata.name, {
  edit: Edit,
  save,  // save: save,
} );

save.js

save.js は、関数 save() をエクスポートします。

この関数は、投稿または固定ページが保存されるときにデータベースの wp_posts テーブルの post_content フィールドに保存されるマークアップを決定し、フロントエンドでのブロックの表示方法と機能を決定します。

edit.js と同様に、ここで定義される関数は通常、block.json で定義された属性(attributes)を含むオブジェクトである属性パラメータを取ります。

この関数は index.js ファイル内の registerBlockType 関数の save プロパティに渡されます。

import { useBlockProps } from '@wordpress/block-editor';

// save 関数をエクスポート
export default function save() {
  return (
    <p { ...useBlockProps.save() }>
      { 'Multi Columns – hello from the saved content!' }
    </p>
  );
}

style.scss

style.scss はブロックエディターとフロントエンドの両方に読み込まれる CSS を含む Sass ファイルです。

エディター内でブロックを異なるように表示する必要がある場合は、ここでのスタイルを editor.scss のスタイルで上書きできます。

このファイルはビルドプロセスで style-index.css に変換されます(style-index.css は block.json で style ファイルとして定義されています)。

.wp-block-create-block-multi-columns {
  background-color: #21759b;
  color: #fff;
  padding: 2px;
}

view.js

view.js は、ブロックが表示されるときにフロントエンド内に読み込まれます。

console.log( 'Hello World! (from create-block-multi-columns block)' );

Edit() は大文字の E なのに、save() は小文字の s

edit.js では export default function Edit() のように Edit() は大文字の E ですが、save.js では export default function save() のように save() は小文字の s になっています。

edit.js でエクスポートしているのは、React コンポーネントになります。React コンポーネントの名前は大文字で始まるのが慣例なので Edit() は大文字の E になっています。

対して、save.js でエクスポートしているのは関数なので(関数の名前は小文字で始まるのが慣例なので)、小文字の s になっています。

プロジェクトをビルドして実行

create-block コマンドを実行してブロックのひな形を作成する際に、レスポンスに以下のような実行可能なコマンドのリストが表示されます。

これらのコマンドは、package.json の scriptsフィールドで定義されている項目に対応しています。

You can run several commands inside:

  $ npm start  // 開始コマンド
    Starts the build for development.

  $ npm run build  // ビルドコマンド
    Builds the code for production.

  $ npm run format
    Formats files.

  $ npm run lint:css
    Lints CSS files.

  $ npm run lint:js
    Lints JavaScript files.

  $ npm run plugin-zip
    Creates a zip file for a WordPress plugin.

  $ npm run packages-update
    Updates WordPress packages to the latest version.

To enter the directory type:

  $ cd multi-columns

You can start development with:

  $ npm start

開始コマンドとビルドコマンド

上記のコマンドのうち最も重要な(よく使用する)コマンドは次の2つです。

npm start npm start は開発中に使用するもので、実行すると src ディレクトリ内のファイルを監視するローカル開発プロセスを開始し、それらのファイルに変更が加えられたときにビルドプロセス(development ビルド)を実行します。
npm run build npm run build は src ディレクトリ内のすべてのコードを取得し、ブロックの最終的な配布可能なビルド(production ビルド)を作成します。

プラグインを有効化

create-block コマンドを実行してブロックのひな形を作成した場合、そのプロセスによってビルド済みバージョンがすでに提供されているため、現時点では npm run build を実行する必要はありません。

代わりに、WordPress 管理画面のプラグインページに移動すると、プラグインがすでに有効化できる状態になっています。

プラグインを有効化して、新しい投稿を作成し、ブロックインサーターを開くと「ウィジェット」の下に「Multi Columns」ブロックがあります。

カテゴリーとアイコン

デフォルトでは、create-block コマンドで作成したブロックのカテゴリーは「ウィジェット(widgets)」に、アイコンは「smiley」に設定されています。

作成時にオプション対話モードでこれらをあらかじめ設定することができますが、後から src ディレクトリ内の block.json ファイル(src/block.json)で変更することができます。

例えば、ブロックを「デザイン(design)」の下に表示し、アイコンを「カラム(columns)」に変更するには、7行目の "category" に "design" を指定し、8行目の "icon" に "columns" を指定します。

アイコンは Dashicons のページで目的のアイコンを探して、アイコンの名前(例 dashicons-columns)の先頭の「dashicons-」 を除いた値を指定します。

必要に応じて description の説明文も変更します。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": false
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

src/block.json を上記のように変更して保存し、投稿を再読込してインサーターを表示しても「Multi Columns」は変更前と変わらず「ウィジェット」の下に表示され、アイコンは「smiley」のままです。

これは、src ディレクトリ内のファイルのみを変更しただけで、変更が build ディレクトリに反映されていないためです。

ちなみに、プラグインファイル multi-columns.php の register_block_type() 関数は、src ディレクトリではなく、build ディレクトリをパラメーターとして受け取ってブロックを登録しています。

ビルド

変更が build ディレクトリにも反映されるようにするには、プロジェクトをビルドする必要があります。そのためには、プロジェクトディレクトリからビルドコマンド(npm run build)を実行します。

プロジェクトディレクトリ(作成したブロックのディレクトリ)に移動します。

% cd multi-columns

ビルドコマンド npm run build を実行します。

% npm run build

ビルドコマンドを実行後、投稿を再読込してインサーターを表示すると以下のように「Multi Columns」ブロックの変更が反映されます。

「Multi Columns」ブロックを投稿に挿入すると、以下のように表示されます。

ここで表示されるテキストは、src/edit.js の以下の4行目に対応しています。

export default function Edit() {
  return (
    <p { ...useBlockProps() }>
      { __( 'Multi Columns – hello from the editor!', 'multi-columns' ) }
    </p>
  );
}

投稿を公開し、投稿のフロントエンドを確認すると以下のように表示されます。

ここで表示されるテキストは、src/save.js の以下の4行目に対応しています。

export default function save() {
  return (
    <p { ...useBlockProps.save() }>
      { 'Multi Columns – hello from the saved content!' }
    </p>
  );
}

このように、edit.js はブロックエディターでコンテンツを編集しているときにエディターに表示される内容を決定し、save.js は WordPress データベースの wp_posts テーブルの post_content フィールドに何が保存されるか、つまりフロントエンドに何が表示されるかを決定します。

開発を開始

ビルドコマンド npm run build を使用してプロジェクトをビルドすると、src ディレクトリ内のファイルが build ディレクトリにコンパイルされ、変更をブラウザーで確認できまます。

但し、コードに変更を加えるたびに、その変更結果をブラウザーで確認するためにビルドコマンドを実行するのは面倒なので、実際の開発では npm start コマンドを使用します。

% npm start

上記コマンドを実行すると、ターミナルにレスポンスが表示され、最後にコンパイル(開発ビルド)が成功したことを示す以下のような行が表示されます。

webpack 5.96.1 compiled successfully in 828 ms

npm run build コマンドを実行したときとは異なり、コマンドプロンプトに戻りません。この場合、プロセスは実行を継続し、src ディレクトリ内のファイルへの変更を監視します(watch モード)。

そして、src ディレクトリ内のファイルが更新されるたびに自動的にリビルドします。そのため、ファイルを変更するたびにビルドコマンドを実行する必要はありません。

終了するには control + c を押します。再開するには npm start を実行します。

edit.js を編集

npm start を実行した状態で、src/edit.js の「Multi Columns」の部分のテキストを、例えば「My Custom Block!!!」に変更します。

export default function Edit() {
  return (
    <p { ...useBlockProps() }>
      { __( 'My Custom Block!!! – hello from the editor!', 'multi-columns' ) }
    </p>
  );
}

変更を保存すると自動的にビルドが実行され、出力の最後に「webpack 5.97.0 compiled successfully in xxx ms」のようなメッセージが表示されます。

エディターを開いて再読込みすると、以下のように変更が反映されています。

但し、save.js は変更していないので、ブラウザでフロントエンドを再読込しても表示は変わりません。

save.js を編集

同様に src/save.js もテキスト部分を編集して保存します。

export default function save() {
  return (
    <p { ...useBlockProps.save() }>
      { 'My Custom Block!!! – hello from the saved content!' }
    </p>
  );
}

edit.js の場合と同様、変更を保存すると自動的にビルドが実行されます。

但し、この時点でブラウザでフロントエンドを再読み込みしても変更が反映されていません。

また、エディターを再読み込みすると以下のように「このブロックには、想定されていないか無効なコンテンツが含まれています。」というバリデーションエラーが表示されます。

このエラーは save.js のコンテンツ(return ステートメント内)に変更を加えたときに、その内容が以前に保存したものと一致しない場合に表示されます。

ブラウザのコンソールでエラーの説明を確認することもできます。この例の場合、以下のように save() 関数に記述されているコンテンツと、投稿コンテンツが異なるというような内容です。

Block validation: Expected text `My Custom Block!!!  – hello from the saved content!`, saw `Multi Columns – hello from the saved content!`.

Block validation failed for `create-block/multi-columns`

Content generated by `save` function:

<p class="wp-block-create-block-multi-columns">My Custom Block!!!  – hello from the saved content!</p>

Content retrieved from post body:

<p class="wp-block-create-block-multi-columns">Multi Columns – hello from the saved content!</p>

save.js の save() 関数の return ステートメント内を変更するとこのバリデーションエラーが発生します。

そのため、開発段階では何度もこのエラーに遭遇しますが、ブロックを完全に構築し終わったときにエラーが出なければ問題ありません。

「復旧を試みる」という青いボタンをクリックしてブロックを復元します。そしてブロックが正しく表示されたら「保存」をクリックして投稿を更新するか、まだ公開していない場合は下書きを保存します。

これにより、save.js の save() 関数が強制的に実行され、変更されたコンテンツがデータベースの wp_posts テーブルに書き込まれます。

フロントエンドを再読み込みすると、save.js に入力した新しいテキストがフロントエンドに表示されます。

save() 関数は、公開済みの固定ページまたは投稿を更新(保存)するとき、またはまだ公開されていない場合は下書きを保存するときにのみ実行されます。

開発は npm start で開始

npm start を使用すると、src ディレクトリにファイルを保存するたびに、プロセスによってビルドステップが実行されるため、プロジェクトに変更を加えるたびに npm run build を実行する手間が省けます。

通常、プラグインの開発中は、npm start を使用し、開発作業が完了し、新しいブロックを公開または配布する準備ができたら、npm run build を使用します。

コンポーネントの追加

現時点では、edit.js と save.js で定義済みのテキストをレンダリングするだけの静的なブロックですが、テキストをユーザーが編集できるように変更します。

以下は現時点での src/edit.js です(元のコメント部分は削除してあります)。

3つのインポートステートメントと、Edit() 関数が記述されています。

Edit() 関数は、エディター(編集画面)がどのようにブロックをレンダリングするかを定義して返す関数(React コンポーネント)です。以下では p 要素をレンダリングする JSX 返しています。

// ローカライズ( テキスト文字列の国際化 )の __() をインポート
import { __ } from '@wordpress/i18n';
// useBlockProps() をインポート
import { useBlockProps } from '@wordpress/block-editor';
// 編集画面用 CSS をインポート
import './editor.scss';

// Edit 関数(コンポーネント)をエクスポート
export default function Edit() {
  // p 要素をレンダリングする JSX を返す
  return (
    // useBlockProps() を展開して、ラッパー要素にエディターで必要とされる属性とクラスなどを出力
    <p { ...useBlockProps() }>
      { __( 'My Custom Block!!! – hello from the editor!', 'multi-columns' ) }
    </p>
  );
}

2行目でインポートしている __ はテキストの翻訳を取得する関数で、__( text, textdomain ) の形式で使用します。(14行目で使われています)。関連項目:国際化

useBlockProps

4行目でインポートしている useBlockProps(React フック)は、ラッパー要素(上記では p 要素)でスプレッド演算子と共に使用すると block.json ファイルで定義された attributes を含むブロックの動作に必要な属性とクラスやイベントハンドラがラッパー要素に挿入されます(13行目)。

ラッパー要素(ブロックラッパー)に追加のカスタム HTML 属性が必要であれば、属性を定義するオブジェクトを useBlockProps() に引数として渡して追加することもできます(HTML 属性の追加)。

通常、ブロックのラッパー要素に {...useBlockProps()} を指定して必要な属性を展開します。

参考:

以下は現時点での src/save.js です(元のコメント部分は削除してあります)。

save() 関数は、フロントエンド側でブロックがどのようにレンダリングされるかを定義して返す関数です。

ブロックのラッパー要素に useBlockProps.save() をスプレッド演算子で展開します。これによりブロックサポート API からの任意の HTML 属性に加え、ブロッククラス名が正しくレンダーされます。

import { useBlockProps } from '@wordpress/block-editor';

// save 関数をエクスポート
export default function save() {
  // p 要素をレンダリングする JSX を返す
  return (
    // useBlockProps().save() をブロックラッパーに展開
    <p { ...useBlockProps.save() }>
      { 'My Custom Block!!! – hello from the saved content!' }
    </p>
  );
}

詳細は以下で確認できます。

JSX

JSX はブラウザでレンダリングされる HTML コードを記述するための React 構文(JavaScript 構文の拡張)です。

JSX のルール

JSX は HTML や XML のタグのように要素を入れ子にしたり、属性を指定することができますが、実際は JavaScript なので JavaScript(JSX)特有の制限や記述方法(文法)があります。

単一のルート要素を返す

コンポーネントから複数の要素を返すには、それを単一の親タグ(div など)で囲みます。

return (
  <div>
    <h1>Header</h1>
    <p>Content</p>
    <span>
      <a href="" />
    </span>
  </div>
)

マークアップに余分な <div> を加えたくない場合は、代わりに <> と </>(フラグメント)を使うことができます。

return (
  <>
    <h1>Header</h1>
    <p>Content</p>
    <span>
      <a href="" />
    </span>
  </>
)

すべてのタグを閉じる

JSX ではすべてのタグを明示的に閉じる必要があります。<img> のような自動で閉じるタグは <img /> のようになります。

属性はキャメルケースで

React では多くの HTML および SVG の属性はキャメルケースで書かれます。例えば stroke-width の代わりに strokeWidth を使います。tabindex は tabIndex(I が大文字)、onclick は onClick になります。

class は JavaScript の予約語なので、React では className を使います。

JSX コンバータ

既存の HTML や SVG を JSX に変換する場合は、必要に応じてコンバータを使うことができます。

波括弧で JavaScript を含める

JSX では、中括弧は JavaScript として評価されるものを表します。そのため、JavaScript コード (変数、条件文、匿名関数など) をレンダリングする必要がある場合は、中括弧 {} で囲みます。

export default function Avatar() {
  const avatar = 'https://i.imgur.com/7vQD0fPs.jpg';
  const description = 'Gregorio Y. Zara';
  return (
    <img
      className="avatar"
      src={avatar}
      alt={description}
    />
  );
}

JSX の詳細は以下の React ドキュメントで確認できます。

RichText コンポーネントを追加

静的なブロックにインタラクティブ機能を追加する(ユーザーがテキストを編集できるようにする)ために、 新しいコンポーネントを追加します。

Gutenberg にはブロックを作成する時に使用できるビルド済みのコンポーネントが多数用意されています。

ブロックで使用できるビルド済みコンポーネントとその使用方法については、Block Editor Handbook の Component Reference を参照してください。

この例では、現在の <p> 要素を、テキストを変更できる <RichText> コンポーネントに置き換えます。

参考:RichText リファレンス

コンポーネントを追加する手順

以下はブロックプラグインに任意のコンポーネントを追加するためのおおまかな手順です。

  1. block.json で
    • このコンポーネントに必要なデータを決定
    • 必要な属性を attributes に定義
  2. edit.js で
    • コンポーネントをインポート
    • 必要な属性を attributes から取得(参照)
    • Edit() 関数によって返される JSX にコンポーネントを追加
    • 属性(attributes)を更新するためのイベントハンドラーを定義
  3. save.js で
    • コンポーネントをインポート
    • 必要な属性を attributes から取得(参照)
    • save() 関数によって返される JSX にコンポーネントを追加

block.json

  • このコンポーネントに必要なデータを決定
  • 必要な属性(attributes)を定義

このブロックで追加する RichText コンポーネントでは、ユーザーが入力するテキストを保存する必要があるので、この場合、必要なデータはユーザーが入力するテキスト文字列になります。

block.json を開き、attributes プロパティにユーザーが入力するテキスト文字列を保存する content という属性を追加します(14-20行目)。

これにより、ユーザーがブロックに入力するテキストを attributes プロパティの content 属性(attributes.content)に保存することができます。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": false
  },
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

attributes(属性)はブロックに必要なデータを定義することができるオブジェクトです。

ブロックは任意の数の属性を含むことができ、属性は attributes フィールドで指定されます。この attributes フィールドは、属性名をキー、属性定義を値にもつオブジェクトです。

上記では属性名を content とし、属性の定義として type に string、source に html、selector に p を指定して、ブロックのマークアップにある p 要素から html 文字列を取り出すように定義しています。

type は属性に格納されているデータの種類を示します(enum が提供されない限り、type は必須です。type は enum と一緒に使用できます)。

ここで定義した attributes は edit.js と save.js でパラメータとして受け取ることができます。

詳細: 属性 | Attributes

edit.js

  • コンポーネントをインポート
  • 必要な属性を attributes から取得(参照)
  • Edit() 関数によって返される JSX にコンポーネントを追加
  • 属性(attributes)を更新するためのイベントハンドラーを追加

edit.js では、まず RichText コンポーネントをインポートします。

RichText コンポーネントは useBlockProps と同じパッケージからインポートできます。

※どのパッケージからインポートするかは、Component Reference のページのサイドバーにコンポーネントがリストされているので、目的のコンポーネントのページで確認できます。

// RichText のインポートを追加
import { useBlockProps, RichText } from "@wordpress/block-editor";

そして Edit 関数のパラメータに属性(attributes)と属性を更新する関数(setAttributes)を分割代入で受け取ります。

export default function Edit( { attributes, setAttributes } ) {
  ...
}

続いて、Edit() 関数によって return される JSX にコンポーネントを追加します。

この場合はブロックのラッパー要素の p 要素を、以下のように RichText コンポーネントで置き換えます。

この変更により、RichText コンポーネントがブロックのラッパー要素になるので、useBlockProps() を RichText コンポーネントに展開します。

// p 要素を RichText コンポーネントに変更
return (
  <RichText
    {...useBlockProps()}
    tagName="p"
    onChange={onChangeContent}
    value={attributes.content}
    placeholder="Enter some text ..."
  />
);

また、以下の RichText コンポーネントのプロパティを設定します。

tagName 入力エリアの HTML タグ(要素)を指定。この例では p 要素を指定。
value RichText コンポーネントに表示される編集可能な値。通常は属性(attributes)に保存して、変更があれば onChange イベントで setAttributes を使って更新します。この例では attributes.content で attributes の content 属性を参照して設定します。
onChange value が変更された際に呼び出されるイベントハンドラ(後で定義)を設定
placeholder プレースホルダーテキスト

onChange イベントで属性 attributes.content を更新するためのイベントハンドラーを追加します。属性の値を更新するには、Edit() のパラメータに受け取った setAttributes を使います。

edit.js は以下のようになります。

import { __ } from "@wordpress/i18n";
// RichText のインポートを追加
import { useBlockProps, RichText } from "@wordpress/block-editor";
import "./editor.scss";

// Edit() のパラメータに attributes と setAttributes を分割代入
export default function Edit( { attributes, setAttributes } ) {
  // attributes の content を更新するためのイベントハンドラーを追加
  const onChangeContent = (val) => {
    //  パラメータに受け取った setAttributes を使って content を更新
    setAttributes({ content: val });  // setAttributes({ 属性の名前: 属性の値 })
  };
  // RichText コンポーネント(JSX)を返す
  return (
    <RichText
      {...useBlockProps()}
      tagName="p"
      onChange={onChangeContent}
      value={attributes.content}
      placeholder="Enter some text ..."
    />
  );
}

属性を更新するためのイベントハンドラーを別途定義せずに、以下のように直接 onChange プロパティに指定することもできます。

export default function Edit( { attributes, setAttributes } ) {
  return (
    <RichText
      {...useBlockProps()}
      tagName="p"
      onChange={(value) => setAttributes({ content: value })}
      value={attributes.content}
      placeholder="Enter some text ..."
    />
  );
}

関数が受け取るパラメータのオブジェクト

edit.js の Edit() 関数(コンポーネント)及び save.js の save() 関数は、パラメータに block.json で定義された attributes や setAttributes などのメソッドを含むオブジェクト(props)を受け取ります。

通常は、上記のように分割代入を使って attributes や setAttributes をパラメータのオブジェクトから受け取りますが、以下のように記述することもできます。

export default function Edit(props) {
  const attributes = props.attributes;
  const setAttributes = props.setAttributes;
  // パラメータに受け取るオブジェクトをコンソールに出力
  console.log(props);
  ・・・以下省略・・・
}

例えば、上記のように記述すると、以下のようにコンソールでパラメータに受け取るオブジェクトを出力することができ、attributes.content や setAttributes が確認できます。

save.js

  • コンポーネントをインポート
  • 必要な属性を attributes から取得(参照)
  • save() 関数によって返される JSX にコンポーネントを追加

新しいコンポーネントのコンテンツが確実に保存されるように save.js を更新します。

edit.js と同様に RichText コンポーネントをインポートします。

return() ステートメントの JSX を p 要素から RichText コンポーネントに変更し、Edit() 関数の場合と同様、スプレッド構文を使って useBlockProps.save() を RichText コンポーネントに展開します。

save 関数で RichText のコンテンツを正しく保存するためには、RichText.Content を使います。

RichText コンポーネントの tagName プロパティに p 要素を指定し、value に save() 関数のパラメータで受け取った attributes を使って属性 content の値(attributes.content)を設定します。

// RichText のインポートを追加
import { useBlockProps, RichText } from "@wordpress/block-editor";

export default function save({ attributes }) {
  // RichText.Content (JSX) を返す
  return (
    <RichText.Content
      {...useBlockProps.save()}
      tagName="p"
      value={attributes.content}
    />
  );
}

エディターを再読み込みして、以下が表示されたら「復旧を試みる」をクリックします。

投稿や固定ページに作成したブロックを新たに挿入すると、以下のようにプレースホルダーテキストが表示され、テキストを挿入することができます。

ツールバーでは、太字や斜体のテキスト、リンクの挿入、その他の書式設定オプションを使用できます。

テキストを入力して書式を設定するなどして保存すると、フロントエンドにも反映されます。

ブロックサポートを追加

カスタマイズオプションのための独自のコントロールをインスペクターに追加することができますが、Gutenberg に同梱されているコアブロックを含む多くのブロックに共通している一般的なカスタマイズオプションであれば supports プロパティ(ブロックサポート API)を使用することができます。

一般的なカスタマイズ オプションの例としては、ブロックのテキストの色や背景色を変更する color オプション、ブロックのパディングとマージンなどを変更する spacing オプションなどがあります。

Gutenberg では、block.json ファイルで support プロパティにオプションを追加するだけで、これらの一般的なカスタマイズオプションを簡単に実装できます。

これらのプロパティは useBlockProps フックに自動的に追加されるため、block.json ファイルで定義して useBlockProps をブロックラッパーに展開する以外に何もする必要はありません(JavaScript や React コードを記述する必要はありません)。

ブロックサポート API の詳細:サポート | Supports

現時点では、以下のようにインスペクターにオプションは表示されていません。

例えば、ユーザーがブロックのテキストの色と背景色を変更できるオプションを追加するには、block.json の supports プロパティに以下のように color プロパティを追加します(13行目)。

この例では color プロパティの値に空のオブジェクトを指定していますが、デフォルトで text と background が true なのでテキストと背景の色のコントロールが表示されます。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": true,
    "color": {}
  },
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

これで以下のようにインスペクターパネルに [色]のコントロールが表示されます。

color プロパティでは値に空のオブジェクトを指定することができますが、プロパティにより指定する値の形式は異なります。例えば、spacing プロパティの場合、以下のようにどの間隔(margin や padding)を有効にするかを個別に指定する必要があります。

また、以下では color に "gradients": true を追加して色の背景色でグラデーションを有効にしています。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": true,
    "color": {
      "gradients": true
    },
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    }
  },
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

上記を保存すると、サイズ(spacing)のコントロールが追加されます。また、背景色を選択すると color に gradients を追加したので「グラデーション」のタブが追加されます。

supports.html

create-block コマンドで生成された block.json の supports プロパティの html プロパティはデフォルトでは false になっています。この例では html プロパティの値を true に変更していますが、必要に応じて変更します(false に戻します)。

html プロパティは「HTMLとして編集」を使用するかどうかの設定で、false にすると、ブロックのオプションに「HTMLとして編集」が表示されません。

デフォルト値を設定

block.json の supports プロパティに追加した color や spacing などのプロパティにデフォルト値を設定することができます(参考例:サポート color.background)。

デフォルト値を設定するには、属性(attributes)に style プロパティ(attributes.style)を追加し、値のオブジェクトに type プロパティと default プロパティを設定します。

必須の type プロパティには "object" を指定し、default プロパティにそれぞれのプロパティのデフォルト値を定義します。

以下は supports プロパティに追加した color プロパティのデフォルト値(テキスト色と背景色)を設定する例です。

"attributes": {
  "content": {
    "type": "string",
    "source": "html",
    "selector": "p"
  },
  "style": {
    "type": "object",
    "default": {
      "color": {
        "text": "#3a3a3a",
        "background": "#f3faf1"
      }
    }
  }
},

block.json ファイルを保存し、エディターでページを再読み込みします。新規に「Multi Columns」ブロックを挿入すると、block.json で指定されたデフォルトの色でカラーコントロールが表示され、それらの色がブロック自体にも適用されます。

既存のブロックがある場合、「このブロックには、想定されていないか無効なコンテンツが含まれています。」というバリデーションエラーが表示されるので、その場合は「復旧を試みる」をクリックします。

同様に、supports プロパティに追加した spacing プロパティにデフォルト値を設定するには attributes.style.default オブジェクトに、spacing プロパティを追加します。

以下は attributes.style.default.spacing に padding を追加してデフォルト値を設定する例です。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    }
  },
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          }
        }
      }
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

バグ?

但上記の方法でデフォルト値を設定した場合、全ての設定をリセットして保存し、再読込するとバリデーションエラーが発生してしまいます(いずれかのみのリセットの場合はエラーは発生しません)。

以下のページに同様の報告がありますが、現時点ではステータスは open(未解決)です。

Resetting default styling in Editor causes block validation error #56657

style.scss

supports プロパティに追加した color や spacing のプロパティにデフォルト値を設定したので、style.scss に記述されている以下のスタイルは上書きされて意味がありません。

 .wp-block-create-block-multi-columns {
    background-color: #21759b;
    color: #fff;
    padding: 2px;
  }

取り敢えず、以下のようにブロックの基本的なスタイルを設定しておきます。

 .wp-block-create-block-multi-columns {
  box-sizing: border-box;
  column-fill: balance; /* デフォルトなので省略可能 */
}

インスペクターパネルの作成

CSS のマルチカラムレイアウトを使用すると、雑誌や新聞記事のような段組みを実装することができます。

例えば、以下のように style.scss に column-count: 4; を追加すると、

 .wp-block-create-block-multi-columns {
  box-sizing: border-box;
  column-fill: balance;
  column-count: 4;  /* 確認用に追加(確認後削除します) */
}

エディターとフロントエンドの両方で以下のように4列の段組みで表示されます。

但し、この場合、ブロックは常に4列で表示されてしまうので、上記の CSS の column-count は削除して、ユーザーが列数を設定できるようにコントロールを追加します。

RangeControl を追加

ユーザーが列の数(カラム数)を設定できるコントロールのコンポーネントを追加します。

以下がコンポーネントの追加手順ですが、ここで追加するコンポーネントはエディターにのみ表示されるので、手順 「3. save.js」のコンポーネントのインポートと追加は必要ありません。

block.json

この場合、コンポーネントに必要なデータはカラム数になります。

カラム数を保存する属性 columnCount を attributes に追加します(26-29)。

この属性の名前 columnCount は、CSS のカラム数を設定するプロパティ column-count をキャメルケースにしたものです。

カラム数は数値(整数)として保存するので、type は integer とし、デフォルト値は4としています。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    }
  },
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    },
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

edit.js

定義した属性 attributes.columnCount を使って、ブロックが段組み表示するようにします。ブロックのラッパー要素に style 属性を出力して column-count の値に columnCount の値を設定するようにします。

そのためには、まず attributes オブジェクトから columnCount 属性を分割代入で取得し、次に useBlockProps フックに渡すスタイルを表すオブジェクト columnStyles を作成します。

// attributes から columnCount を分割代入
const { columnCount } = attributes;
// インラインスタイル(column-count: 列数)を表すオブジェクト
const columnStyles = { columnCount }; // columnStyles = { columnCount: columnCount } と同じ

ブロックのラッパー要素に HTML 属性(この場合は style 属性)を追加するには、useBlockProps() にパラメータとして style 属性を定義するオブジェクト {style: スタイルのオブジェクト} を渡します。

スタイルのオブジェクトの { columnCount } は短縮構文なので { columnCount: columnCount } と同じですが、内部で CSS に変換される際は { column-count: columnCount 属性の値 } となります。

Edit() に上記のコードを追加し、useBlockProps() にパラメータとして { style: columnStyles } を渡します。これによりラッパー要素の style 属性に「column-count: columnCount の値」が出力されます。

edit.js は以下のようになります。

import { __ } from "@wordpress/i18n";
// RichText のインポートを追加
import { useBlockProps, RichText } from "@wordpress/block-editor";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  // attributes から columnCount を分割代入
  const { columnCount } = attributes;
  // インラインスタイル(column-count:列数)を表すオブジェクト
  const columnStyles = { columnCount };

  const onChangeContent = (val) => {
    setAttributes({ content: val });
  };

  return (
    <RichText
      {...useBlockProps( { style: columnStyles } )}
      tagName="p"
      onChange={onChangeContent}
      value={attributes.content}
      placeholder="Enter some text ..."
    />
  );
}

現時点では、ブロックは block.json にハードコードされた columnCount のデフォルト値 4 を使用しているので、エディターでページを更新すると、ブロックは4列で表示されます。

数値を選択するコントロールのコンポーネントを追加

カラム数(属性 columnCount の値)は、この例では 2 - 6 の範囲(2列〜6列表示)とします。

ユーザーが 2 から 6 までの整数値を選択して、columnCount 属性を更新できるコントロールとしては、RangeControl コンポーネントが適しています。

この例では RangeControl の以下のプロパティを使用します。

RangeControl のプロパティ(一部抜粋)
label コントロールに表示されるラベル
value レンジスライダーの現在の値(数値)
onChange レンジスライダーの値が変更された時に新しい値を受け取る関数
min 最小値(Default: 0)
max 最大値(Default: 100)

edit.js を以下のように書き換えます。

まず、追加するコンポーネント RangeControl をインポートします(4行目)。

次に、Edit() 関数によって返される JSX に RangeControl コンポーネントを追加します(22-28行目)。

この時 JSX 全体をフラグメント (<>〜</>) で囲む必要があります(21, 36行目)。

RangeControl コンポーネントの label には「Columns(列数)」を指定し、value には8行目で attributes から分割代入した columnCount を指定します。

onChange には次に定義するイベントハンドラー onChangeColumnCount を指定し、min と max に最小値(2)と最大値(6)を指定します。

そして、属性を更新するためのイベントハンドラー onChangeColumnCount の定義を setAttributes() を使って追加します(15-17行目)。

import { __ } from "@wordpress/i18n";
import { useBlockProps, RichText } from "@wordpress/block-editor";
// RangeControl のインポートを追加
import { RangeControl } from "@wordpress/components";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  const { columnCount } = attributes;
  const columnStyles = { columnCount };

  const onChangeContent = (val) => {
    setAttributes({ content: val });
  };
  // 属性 columnCount を更新するためのイベントハンドラーを追加
  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };

  // JSX に RangeControl コンポーネントを追加。全体をフラグメント (<>〜</>) で囲む
  return (
    <>
      <RangeControl
        label="Columns"
        value={columnCount}
        onChange={onChangeColumnCount}
        min={2}
        max={6}
      />
      <RichText
        {...useBlockProps({ style: columnStyles })}
        tagName="p"
        onChange={onChangeContent}
        value={attributes.content}
        placeholder="Enter some text ..."
      />
    </>
  );
}

上記を保存して、エディターを再読込すると以下のように表示されます。コントロールが追加され、スライダーを動かすと値が変わり、それに応じてブロック内の列数も変わります。

但し、レンジスライダーがインスペクターパネルのサイドバーではなく、ブロックの上に表示されます。

InspectorControls を追加

インスペクターにコントロールを表示するには InspectorControls コンポーネントを使用します。

Edit() 関数の return の中で InspectorControls コンポーネントで囲んだ内容はサイドバーのインスペクターに表示され、独自のカスタムパネルを作成できます。

通常、InspectorControls コンポーネントの中にパネルのコンポーネント PanelBody を配置し、その中にコントロールのコンポーネントを配置します。

PanelBody にはパネルのタイトルを設定する title プロパティやパネルの開閉状態を設定する initialOpen プロパティなどを指定することができ、関連するコントロールをまとめて配置することができます。

参考:Panel

edit.js を以下のように書き換えます。

InspectorControls と PanelBody を追加でインポートします(3行目と5行目)。

そして、RangeControl コンポーネントを PanelBody コンポーネントと InspectorControls コンポーネントで囲みます。

PanelBody コンポーネントの title プロパティにはパネルに表示されるタイトルを設定でき、「列の設定」のパネルなので「Column Settings」としています(23行目)。

import { __ } from "@wordpress/i18n";
// InspectorControls のインポートを追加
import { useBlockProps, RichText, InspectorControls,} from "@wordpress/block-editor";
// PanelBody のインポートを追加
import { PanelBody, RangeControl } from "@wordpress/components";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  const { columnCount } = attributes;
  const columnStyles = { columnCount };

  const onChangeContent = (val) => {
    setAttributes({ content: val });
  };
  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };

  // RangeControl コンポーネントを PanelBody と InspectorControls で囲む
  return (
    <>
      <InspectorControls>
        <PanelBody title="Column Settings">
          <RangeControl
            label="Columns"
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
        </PanelBody>
      </InspectorControls>
      <RichText
        {...useBlockProps({ style: columnStyles })}
        tagName="p"
        onChange={onChangeContent}
        value={attributes.content}
        placeholder="Enter some text ..."
      />
    </>
  );
}

InspectorControls コンポーネントは一度だけ追加し、追加するコントロールは全て同じ1つの InspectorControls コンポーネントでラップします。

PanelBody コンポーネントは、複数回使用してコントロールをグループ化できます。

上記を保存して、エディターを再読込すると以下のようにレンジスライダーがインスペクターパネルのサイドバーに表示され、PanelBody の title プロパティの値(Column Settings)がパネル上部に表示されます。

ブロックサポートで追加したスタイルの設定はスタイルのアイコンをクリックすると表示されます。

ブラウザのインスペクターパネルで出力されたブロックのマークアップを確認すると、以下のように RichText コンポーネントにより出力される p 要素の style 属性に、useBlockProps() にパラメータとして渡した { style: columnStyles } によって column-count: 3 が出力されています(その他にブロックサポートで追加した color や margin などのスタイルも出力されています)。

<p role="document"
  aria-multiline="true"
  class="block-editor-rich-text__editable block-editor-block-list__block wp-block is-selected wp-elements-1 has-text-color has-background wp-block-create-block-multi-columns rich-text"
  id="block-74a98a1d-ee28-432f-871b-d80af06239e6"
  aria-label="ブロック: Multi Columns"
  data-block="74a98a1d-ee28-432f-871b-d80af06239e6"
  data-type="create-block/multi-columns"
  data-title="Multi Columns"
  contenteditable="true"
  style="column-count: 3; white-space: pre-wrap; min-width: 1px; color: rgb(58, 58, 58); background-color: rgb(243, 250, 241); margin: 20px 0px; padding: 20px;"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit...
</p>

save.js を編集

現時点では更新内容がフロントエンドに表示されません。続いて save.js を編集します。

エディターで追加したコンポーネント(RangeControl、PanelBody、InspectorControls)はエディターにのみ表示されるため、save.js にインポートする必要はありません。

save.js では、edit.js で行ったのと同様に、attributes オブジェクトから columnCount 属性を分割代入で取得し、columnStyles オブジェクトを作成して、それを useBlockProps.save() に渡してラッパー要素に展開します。

import { useBlockProps, RichText } from "@wordpress/block-editor";

export default function save({ attributes }) {
  // attributes から columnCount を分割代入
  const { columnCount } = attributes;
  // インラインスタイル(column-count:列数)を表すオブジェクトを定義
  const columnStyles = { columnCount}

  // useBlockProps.save にパラメータとしてスタイルのオブジェクトを渡す
  return (
    <RichText.Content
      {...useBlockProps.save({style: columnStyles})}
      tagName="p"
      value={attributes.content}
    />
  );
}

上記を保存して、エディターを再読込すると save.js のコンテンツを変更したため、以下のエラーが表示されますが、「復旧を試みる」をクリックしてブロックを回復させて保存します。

save.js に変更を加えてファイルを保存したら、エディターで投稿を再読み込みしてから投稿を更新する必要があります。その後、save() 関数の新しいバージョンが実行され、変更がフロントエンドに表示されます。

フロントエンドを確認すると、以下のように指定したカラム数で段組みされて表示されます。

コントロールを追加

CSS のマルチカラムレイアウトでは、カラム数を設定する column-count の他にカラムの幅を設定する column-width、カラム間の余白を設定する column-gap、罫線のスタイルを設定する column-rule などを指定することができます。以下ではこれらのプロパティを設定するコントロールを追加します。

以下を style.scss に記述してこれらの CSS プロパティによる表示を確認することができます。

.wp-block-create-block-multi-columns {
  box-sizing: border-box;
  column-fill: balance;
  /* 以下は確認用のスタイル(後で削除します) */
  column-width: 180px;
  column-gap: 2.5rem;
  column-rule: 1px solid #b7bd8a;
}

上記を保存してエディターを再読込すると以下のように表示されます。

前項で追加したカラム数のコントロールには4を指定していますが、この例の場合、column-width: 180px を指定しているので、こちらが優先され、カラム数の指定は最大値となり、以下のように3列になります。

各値を変更してどのように動作するかを確認したら、style.scss から確認用のスタイルは削除します。

NumberControl(数値入力コントロール)

まずは、column-width(カラム幅)を設定するコントロールのコンポーネントを追加します。

繰り返しになりますが、以下がコンポーネントを追加する際の手順です。

block.json

カラム幅のコントロールは CSS の column-width プロパティの値を設定するので、整数値のデータが必要になり、属性名はキャメルケースにした columnWidth とし、デフォルト値は 200(px)とします。

attributes に type が integer、で default が 200 の columnWidth 属性を定義します(30-33)。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    }
  },
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    },
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

edit.js

数値を入力する type="number" 属性を持つ input 要素のコンポーネントがあればよいのですが、現時点では Gutenberg にはこれを実装するコンポーネントがありません。

※ 数値入力用のコンポーネント __experimentalNumberControl がありますが、実験的なコンポーネントなので、将来廃止されるか API が変更されるか、または別のコンポーネントに置き換えられる可能性があるため、特に本番コードでは実験的なコンポーネントに依存するのは得策ではありません。

この例では、参考元のチュートリアルで提供されている以下のカスタム NumberControl コンポーネントを使用します。ファイルとして保存して、通常のコンポーネントと同様に使用できます。

import { BaseControl } from "@wordpress/components";

  const NumberControl = (props) => {
    const { min, max, step, value, onChange, label, ...additionalProps } = props;
    const minlength = min.toString().length;

    return (
      <BaseControl label={label}>
        <input
          type="number"
          min={min}
          max={max}
          step={step}
          value={value}
          onChange={(e) => {
            let val = e.target.value;
            if (e.target.value.length >= minlength) {
              val = val > max ? max : val;
              val = val < min ? min : val;
            }
            onChange(val);
          }}
          onBlur={(e) => {
            let val = e.target.value;
            if (e.target.value.length < minlength) {
              val = min;
            }
            onChange(val);
          }}
          style={{ marginLeft: "8px" }}
        />
      </BaseControl>
    );
  };

  export default NumberControl;
// BaseControl コンポーネントを WordPress の @wordpress/components パッケージからインポート
import { BaseControl } from "@wordpress/components";

/**
 * NumberControl コンポーネント
 * このコンポーネントは数値入力フィールドを提供し、値の範囲やステップを制御できます。
 *
 * @param {Object} props - コンポーネントに渡されるプロパティ
 * @param {number} props.min - 入力可能な最小値
 * @param {number} props.max - 入力可能な最大値
 * @param {number} props.step - 入力値のステップサイズ
 * @param {number} props.value - 現在の入力値
 * @param {Function} props.onChange - 入力値が変更された際に呼び出されるコールバック関数
 * @param {string} props.label - コントロールに表示されるラベル
 * @param {Object} [props.additionalProps] - その他の追加プロパティ
 */

// NumberControl コンポーネントの定義
const NumberControl = (props) => {
  // props から渡される値を分割代入して取得
  const { min, max, step, value, onChange, label, ...additionalProps } = props;

  // 最小値 (min) の文字数を取得
  // 入力値がこの文字数に満たない場合のバリデーションに利用
  const minlength = min.toString().length;

  // JSX のレンダリング部分
  return (
    // BaseControl はラベルと入力エリアを整列させるための WordPress コンポーネント
    <BaseControl label={label}>
      {/* HTML の <input> 要素を使用して数値入力フィールドを作成 */}
      <input
        type="number" // 入力が数値のみであることを指定
        min={min} // 入力可能な最小値
        max={max} // 入力可能な最大値
        step={step} // ステップサイズを指定
        value={value} // 現在の入力値
        // 値が変更されたときに呼び出されるイベントハンドラー
        onChange={(e) => {
          // 入力値を取得
          let val = e.target.value;
          // 入力値の文字数が minlength 以上であれば検証
          if (e.target.value.length >= minlength) {
            // 最大値を超えた場合は最大値に設定
            val = val > max ? max : val;
            // 最小値を下回った場合は最小値に設定
            val = val < min ? min : val;
          }
          // 親コンポーネントに新しい値を通知(親コンポーネントに値を伝えるコールバック関数を実行)
          onChange(val);
        }}
        // フォーカスが外れた際のイベントハンドラー
        onBlur={(e) => {
          // 入力値を取得
          let val = e.target.value;
          // 入力値が minlength より短い場合は最小値に設定
          if (e.target.value.length < minlength) {
            val = min;
          }
          // 親コンポーネントに新しい値を通知
          onChange(val);
        }}
        // スタイルを直接設定
        style={{ marginLeft: "8px" }} // 入力エリアの左に余白を追加
        {...additionalProps} // その他の追加プロパティを適用
      />
    </BaseControl>
  );
};

// このコンポーネントをエクスポート
export default NumberControl;

プロジェクトの src ディレクトリに新しいディレクトリ components を作成し、そのディレクトリ内に number-control.js という新しいファイルを作成して上記をコピーしてペーストします。

edit.js ではコードをコピーして作成したファイル number-control.js で定義されている NumberControl コンポーネントをインポートします。

import NumberControl from "./components/number-control";

そして Edit() 関数で、block.json に追加した columnWidth 属性を attributes から分割代入で取得し、それをスタイルを定義する columnStyles オブジェクトに追加します。

// attributes から columnWidth を分割代入して取得
const { columnCount, columnWidth } = attributes;
// columnWidth をスタイルのオブジェクト columnStyles に追加
const columnStyles = { columnCount, columnWidth };

上記の4行目は以下と同じことです。

const columnStyles = {
  columnCount: columnCount,
  columnWidth: columnWidth
};

次に Edit() 関数によって返される JSX に NumberControl コンポーネントを追加します(39-46)。

このコンポーネントには、label や min、max、および step プロパティを指定します。label は幅のコントロールなので Width とし、最小値の min は 120(px),最大値の max は 500(px)、step は 10 としています。

value プロパティは属性オブジェクト columnWidth から取得し、onChange にはイベントハンドラー関数への参照を渡します。

イベントハンドラーの定義では setAttributes() を使って columnWidth を更新します(22-25)。

※ この例で使用しているカスタム NumberControl コンポーネントは type="number" 属性を持つ input 要素で作成されているのでテキストを返します。そのため、block.json で定義されている形式 (整数) で保存するには、Number() 関数を使用してテキストを整数にキャストする必要があります(24行目)。

edit.js は以下のようになります。

import { __ } from "@wordpress/i18n";
import { useBlockProps, RichText, InspectorControls,} from "@wordpress/block-editor";
import { PanelBody, RangeControl } from "@wordpress/components";
// NumberControl のインポートを追加
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  // attributes から columnWidth を分割代入して追加
  const { columnCount, columnWidth } = attributes;
  // columnWidth をスタイルのオブジェクト columnStyles に追加
  const columnStyles = { columnCount, columnWidth };

  const onChangeContent = (val) => {
    setAttributes({ content: val });
  };
  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };

  // 属性 columnWidth を更新するためのイベントハンドラーを追加
  const onChangeColumnWidth = (val) => {
    // val はテキストなので整数にキャストします
    setAttributes({ columnWidth: Number(val) });
  };

  // PanelBody に NumberControl コンポーネントを追加
  return (
    <>
      <InspectorControls>
        <PanelBody title="Column Settings">
          <RangeControl
            label="Columns"
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label="Width"
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
        </PanelBody>
      </InspectorControls>
      <RichText
        {...useBlockProps({ style: columnStyles })}
        tagName="p"
        onChange={onChangeContent}
        value={attributes.content}
        placeholder="Enter some text ..."
      />
    </>
  );
}

カスタム NumberControl コンポーネントの代わりに、__experimentalNumberControl(実験的なコンポーネント)を使う場合は、5行目のインポートを以下に書き換えます。

import { __experimentalNumberControl as NumberControl, PanelBody, RangeControl } from '@wordpress/components';

save.js

edit.js で追加した NumberControl コンポーネントはエディターにのみ表示され、フロントエンドには表示されないため、save.js でコンポーネントを追加する必要はありません。

save.js では、attributes オブジェクトから columnWidth 属性を分割代入で取得し、それを columnStyles オブジェクトに追加するだけです。

import { useBlockProps, RichText } from "@wordpress/block-editor";

export default function save({ attributes }) {
  // attributes から columnWidth を分割代入で取得
  const { columnCount, columnWidth } = attributes;
  // スタイルを表すオブジェクトに columnWidth を追加
  const columnStyles = { columnCount, columnWidth };

  return (
    <RichText.Content
      {...useBlockProps.save({ style: columnStyles })}
      tagName="p"
      value={attributes.content}
    />
  );
}

save.js に変更を加えてファイルを保存したら、エディターで投稿を再読み込みしてから投稿を更新します。

「このブロックには、想定されていないか無効なコンテンツが含まれています」と表示されたら、「復旧を試みる」をクリックします。

インスペクターパネルに、カラム幅を設定するコントロールが追加されます(こちらの設定がカラム数の設定を優先します)。変更するとフロントエンドにも反映されることを確認します。

column-gap を追加

続いてカラム間の余白(CSS column-gap)を設定するコントロールを追加します。

まず、保存する必要があるデータを決定し、block.json に属性を定義します。

この場合、カラム間の余白 column-gap の値なので属性名は columnGap とします。値は整数になるため type を integer とし、適切なデフォルト値(この例では40)を指定します(34-37)

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    }
  },
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    },
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "columnGap": {
      "type": "integer",
      "default": 40
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

追加するコントロールは、カラム幅と同様、NumberControl コンポーネントを使用します。

edit.js では、NumberControl コンポーネントはすでにインポートされているため、再度インポートする必要はありません。

Edit() 関数によって返される JSX の PanelBody コンポーネント内に NumberControl コンポーネントを追加し、label(ラベル)や min(最小値)、max(最大値)プロパティを適宜設定します。

attributes から columnGap 属性を分割代入で取得し、columnStyles に追加します。

onChange イベントのハンドラーの定義では、columnWidth の場合、同様、値を整数にキャストします。

import { __ } from "@wordpress/i18n";
import { useBlockProps, RichText, InspectorControls, } from "@wordpress/block-editor";
import { PanelBody, RangeControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  // attributes から columnGap を分割代入して取得
  const { columnCount, columnWidth, columnGap } = attributes;
  // columnGap をスタイルのオブジェクト columnStyles に追加
  const columnStyles = { columnCount, columnWidth, columnGap };
  const onChangeContent = (val) => {
    setAttributes({ content: val });
  };
  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  // 属性 columnWidth を更新するためのイベントハンドラーを追加
  const onChangeColumnGap = (val) => {
    // val はテキストなので整数にキャストします
    setAttributes({ columnGap: Number(val) });
  };

  // PanelBody に NumberControl コンポーネントを追加
  return (
    <>
      <InspectorControls>
        <PanelBody title="Column Settings">
          <RangeControl
            label="Columns"
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label="Width"
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label="Gap"
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
      </InspectorControls>
      <RichText
        {...useBlockProps({ style: columnStyles })}
        tagName="p"
        onChange={onChangeContent}
        value={attributes.content}
        placeholder="Enter some text ..."
      />
    </>
  );
}

save.js では、attributes から columnGap 属性を分割代入で取得し、それを columnStyles に追加して、値がコンテンツとともに保存され、フロントエンドのブロックにスタイルが設定されるようにします。

import { useBlockProps, RichText } from "@wordpress/block-editor";

export default function save({ attributes }) {
  // attributes から columnGap を分割代入で取得
  const { columnCount, columnWidth, columnGap } = attributes;
  // スタイルを表すオブジェクトに columnGap を追加
  const columnStyles = { columnCount, columnWidth, columnGap };

  return (
    <RichText.Content
      {...useBlockProps.save({ style: columnStyles })}
      tagName="p"
      value={attributes.content}
    />
  );
}

save.js を保存したら、エディターで投稿を再読み込みしてから投稿を更新し、バリデーションエラーが表示されたら「復旧を試みる」をクリックします。

インスペクターパネルには、「Gap」のコントロールが追加されます。

SelectControl を追加

CSS のマルチカラムレイアウトの column-rule(段組みの各段の間に表示する罫線・区切り線のプロパティ)を設定するコントロールを作成します。

column-rule プロパティは以下の3つのプロパティのショートハンドプロパティなので、コントロールはそれぞれに対して作成します(※ column-rule-style が設定されていない場合は区切り線は表示されません)。

  • column-rule-style
  • column-rule-width
  • column-rule-color

まず、区切り線を表示するために必須の column-rule-style のコントロールを作成します。

繰り返しになりますが、以下がコンポーネントを追加する際の手順です。

block.json

column-rule-style は、solid、dotted、dashed などの文字列の値を取ることができるので、type が「string」の属性が必要になります。また、「solid」をデフォルトとします。

block.json で属性 columnRuleStyle を attributes プロパティに定義します(38-41)。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    }
  },
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    },
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "columnGap": {
      "type": "integer",
      "default": 40
    },
    "columnRuleStyle": {
      "type": "string",
      "default": "solid"
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

edit.js

column-rule-style の値には、solid、dotted、dashed などの文字列が指定できますが、ユーザーがタイプミスする可能性や使い勝手を考えて、オプションメニューから選択する select 要素を使った SelectControl コンポーネントを利用します。

edit.js で SelectControl コンポーネントをインポートします。

// SelectControl を追加でインポート
import { PanelBody, RangeControl, SelectControl } from '@wordpress/components';

そして Edit() 関数で、attributes から columnRuleStyle を分割代入で取得し、それをスタイルを定義する columnStyles オブジェクトに追加します。

// attributes から columnRuleStyle を分割代入して取得
const { columnCount, columnWidth, columnGap, columnRuleStyle } = attributes;
// columnGap をスタイルのオブジェクト columnStyles に追加
const columnStyles = { columnCount, columnWidth, columnGap, columnRuleStyle };

次は、Edit() 関数にコンポーネントの JSX を追加します。既存の PanelBody コンポーネントに他の設定と一緒に配置することもできますが、区切り線に関連するコントロールを別の新しい PanelBody コンポーネントに配置して視覚的に区別し、コントロールを整理します。

この例では新しい PanelBody の title は「Column Separator」にしています。

新しい PanelBody コンポーネント内に SelectControl コンポーネントを追加します(58-94)。

SelectControl コンポーネントの label には「Style」を指定して、onChange には別途定義するハンドラー関数を指定し、value には分割代入で取得した columnRuleStyle を指定します。options(セレクト要素のオプション)には、none を含む全てのオプションのリストを設定します。

そして onChange イベントのハンドラー関数を定義します(26-28)。

import { __ } from "@wordpress/i18n";
import { useBlockProps, RichText, InspectorControls, } from "@wordpress/block-editor";
// SelectControl を追加でインポート
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  // attributes から columnRuleStyle を分割代入して取得
  const { columnCount, columnWidth, columnGap, columnRuleStyle } = attributes;
  // columnRuleStyle をスタイルのオブジェクト columnStyles に追加
  const columnStyles = { columnCount, columnWidth, columnGap, columnRuleStyle };
  const onChangeContent = (val) => {
    setAttributes({ content: val });
  };
  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  // 属性 columnRuleStyle を更新するためのイベントハンドラーを追加
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };

  // 新たに PanelBody を追加してその中に SelectControl コンポーネントを追加
  return (
    <>
      <InspectorControls>
        <PanelBody title="Column Settings">
          <RangeControl
            label="Columns"
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label="Width"
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label="Gap"
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody title="Column Separator">
          <SelectControl
            label="Style"
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: "None",
                value: "none",
              },
              {
                label: "Solid",
                value: "solid",
              },
              {
                label: "Dotted",
                value: "dotted",
              },
              {
                label: "Dashed",
                value: "dashed",
              },
              {
                label: "Double",
                value: "double",
              },
              {
                label: "Groove",
                value: "groove",
              },
              {
                label: "Ridge",
                value: "ridge",
              },
            ]}
          />
        </PanelBody>
      </InspectorControls>
      <RichText
        {...useBlockProps({ style: columnStyles })}
        tagName="p"
        onChange={onChangeContent}
        value={attributes.content}
        placeholder="Enter some text ..."
      />
    </>
  );
}

save.js

save.js では、コンポーネントはエディターにのみ表示されるため、コンポーネントを追加する必要はありません。必要なのは、attributes から属性 columnRuleStyle を分割代入で取得し、useBlockProps.save() に渡される columnStyles オブジェクトに追加するだけです。

import { useBlockProps, RichText } from "@wordpress/block-editor";

export default function save({ attributes }) {
  // attributes から columnRuleStyle を分割代入で取得
  const { columnCount, columnWidth, columnGap, columnRuleStyle } = attributes;
  // スタイルを表すオブジェクトに columnRuleStyle を追加
  const columnStyles = { columnCount, columnWidth, columnGap, columnRuleStyle };

  return (
    <RichText.Content
      {...useBlockProps.save({ style: columnStyles })}
      tagName="p"
      value={attributes.content}
    />
  );
}

save.js を保存したら、エディターで投稿を再読み込みしてから投稿を更新し、バリデーションエラーが表示されたら「復旧を試みる」をクリックします。

インスペクターの「Column Separator」パネルに「Style」といセレクトコントロールが表示されます。

ユーザーは Style のセレクトボックス(オプションメニュー)から値を選択できます。

column-rule-width

インスペクターパネルに区切り線の幅を設定するコントロールを追加します。手順は今までと同じです。

block.json で column-rule-width(区切り線の幅)を保存する属性 columnRuleWidth を定義します。値は整数値になるため type は integer、デフォルトは 1 にします(42-45)。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    }
  },
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    },
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "columnGap": {
      "type": "integer",
      "default": 40
    },
    "columnRuleStyle": {
      "type": "string",
      "default": "solid"
    },
    "columnRuleWidth": {
      "type": "integer",
      "default": 1
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

カスタム NumberControl コンポーネントを再度使うので、edit.js ではコンポーネントをインポートする必要はありません。

attributes から columnRuleWidth 属性を分割代入で取得し、columnStyles に追加します。

// attributes から columnRuleWidth を分割代入して取得
const { columnCount, columnWidth, columnGap, columnRuleStyle, columnRuleWidth } = attributes;
// columnRuleWidth をスタイルのオブジェクト columnStyles に追加
const columnStyles = { columnCount, columnWidth, columnGap, columnRuleStyle, columnRuleWidth };

Edit() 関数によって返される JSX の2つ目の PanelBody コンポーネント内に NumberControl コンポーネントを追加し、プロパティを適宜設定します。

<NumberControl
  label="Width"
  onChange={ onChangeColumnRuleWidth }
  value={ columnRuleWidth }
  min={ 1 }
  max={ 8 }
/>

onChange イベントのハンドラーの定義では、Number() を使って値を整数にキャストします。

const onChangeColumnRuleWidth = (val) => {
  // 値を整数にキャスト
  setAttributes({ columnRuleWidth: Number(val)  });
};
import { __ } from "@wordpress/i18n";
import { useBlockProps, RichText, InspectorControls, } from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  // attributes から columnRuleWidth を分割代入して取得
  const { columnCount, columnWidth, columnGap, columnRuleStyle, columnRuleWidth } = attributes;
  // columnRuleWidth をスタイルのオブジェクト columnStyles に追加
  const columnStyles = { columnCount, columnWidth, columnGap, columnRuleStyle, columnRuleWidth };
  const onChangeContent = (val) => {
    setAttributes({ content: val });
  };
  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  // 属性 columnRuleWidth を更新するためのイベントハンドラーを追加
  const onChangeColumnRuleWidth = (val) => {
    // 値を整数にキャスト
    setAttributes({ columnRuleWidth: Number(val)  });
  };

  // 2つ目の PanelBody に NumberControl コンポーネントを追加
  return (
    <>
      <InspectorControls>
        <PanelBody title="Column Settings">
          <RangeControl
            label="Columns"
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label="Width"
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label="Gap"
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody title="Column Separator">
          <SelectControl
            label="Style"
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: "None",
                value: "none",
              },
              {
                label: "Solid",
                value: "solid",
              },
              {
                label: "Dotted",
                value: "dotted",
              },
              {
                label: "Dashed",
                value: "dashed",
              },
              {
                label: "Double",
                value: "double",
              },
              {
                label: "Groove",
                value: "groove",
              },
              {
                label: "Ridge",
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label="Width"
            onChange={ onChangeColumnRuleWidth }
            value={ columnRuleWidth }
            min={ 1 }
            max={ 8 }
          />
        </PanelBody>
      </InspectorControls>
      <RichText
        {...useBlockProps({ style: columnStyles })}
        tagName="p"
        onChange={onChangeContent}
        value={attributes.content}
        placeholder="Enter some text ..."
      />
    </>
  );
}

以下は save.js です。属性 columnRuleWidth を取得して columnStyles に追加します。

import { useBlockProps, RichText } from "@wordpress/block-editor";

export default function save({ attributes }) {
  // attributes から columnRuleWidth を分割代入で取得
  const { columnCount, columnWidth, columnGap, columnRuleStyle, columnRuleWidth } = attributes;
  // スタイルを表すオブジェクトに columnRuleWidth を追加
  const columnStyles = { columnCount, columnWidth, columnGap, columnRuleStyle, columnRuleWidth };

  return (
    <RichText.Content
      {...useBlockProps.save({ style: columnStyles })}
      tagName="p"
      value={attributes.content}
    />
  );
}

save.js を保存したら、エディターで投稿を再読み込みしてから投稿を更新し、バリデーションエラーが表示されたら「復旧を試みる」をクリックします。

インスペクターの「Column Separator」パネルに「Width」の コントロールが表示されます。

PanelBody initialOpen プロパティ

インスペクターパネルのコントロールが増えたので、少しごちゃごちゃしてきました。

あまり重要でないコントロールを initialOpen プロパティを使って、最初は非表示にすることでパネルを少し整理することができます。この時、関連するコントロールをまとめてそれぞれの PanelBody コンポーネントに配置しておくのがポイントです。

PanelBody コンポーネントは initialOpen プロパティを受け取り、このプロパティは真偽値を受け取ります。これを「Column Separator」の PanelBody コンポーネントに追加します。

<PanelBody title="Column Separator" initialOpen={false}>

これで、ブロックが投稿や固定ページに追加されると「Column Separator」パネルは最初は非表示になり、右側のアイコンをクリックすると開きます。

PanelColorSettings

区切り線の色(column-rule-color)を変更するオプションのコントロールを表示するため、PanelColorSettings コンポーネントを追加します。

PanelColorSettings は、さまざまな色設定を管理するための UI(カラーコントロールパネル)をレンダリングする React コンポーネントです。

コンポーネントの追加手順

まず必要なデータを決定します。この属性は、色の値を 16 進数形式(例: #b7bd8a)で保存するため、文字列型のデータが必要なので type に string を設定します。

任意のデフォルト値(色の値)を指定して、block.json で属性 columnRuleColor を定義します。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    }
  },
  "attributes": {
    "content": {
      "type": "string",
      "source": "html",
      "selector": "p"
    },
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "columnGap": {
      "type": "integer",
      "default": 40
    },
    "columnRuleStyle": {
      "type": "string",
      "default": "solid"
    },
    "columnRuleWidth": {
      "type": "integer",
      "default": 1
    },
    "columnRuleColor": {
      "type": "string",
      "default": "#b8b8b8"
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

edit.js で PanelColorSettings コンポーネントをインポートします。

// PanelColorSettings を追加でインポート
import { useBlockProps, RichText, InspectorControls, PanelColorSettings,} from '@wordpress/block-editor';

そして Edit() 関数で、attributes から columnRuleColor を分割代入で取得し、取得した columnRuleColor をスタイルを定義する columnStyles オブジェクトに追加します。

// attributes から columnRuleColor を分割代入して取得
const {
  columnCount,
  columnWidth,
  columnGap,
  columnRuleStyle,
  columnRuleWidth,
  columnRuleColor,
} = attributes;

// columnRuleColor をスタイルのオブジェクト columnStyles に追加
const columnStyles = {
  columnCount,
  columnWidth,
  columnGap,
  columnRuleStyle,
  columnRuleWidth,
  columnRuleColor,
};

属性 columnRuleColor を更新するためのイベントハンドラーを定義します。

// 属性 columnRuleColor を更新するためのイベントハンドラーを追加
const onChangeColumnRuleColor = (val) => {
  setAttributes({ columnRuleColor: val });
};

Edit() 関数に PanelColorSettings コンポーネントの JSX を追加します。

※ PanelColorSettings コンポーネントは、独自のパネル本体を提供するため、PanelBody コンポーネントに含める必要はありません。

そのため、PanelColorSettings コンポーネントを InspectorControls コンポーネント内の最後の PanelBody コンポーネントの後に追加します(122-131)。

import { __ } from "@wordpress/i18n";
// PanelColorSettings を追加でインポート
import { useBlockProps, RichText, InspectorControls, PanelColorSettings } from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  // attributes から columnRuleColor を分割代入して取得
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  } = attributes;
  // columnGap をスタイルのオブジェクト columnRuleColor に追加
  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  };
  const onChangeContent = (val) => {
    setAttributes({ content: val });
  };
  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  const onChangeColumnRuleWidth = (val) => {
    setAttributes({ columnRuleWidth: Number(val) });
  };
  // 属性 columnRuleColor を更新するためのイベントハンドラーを追加
  const onChangeColumnRuleColor = (val) => {
    setAttributes({ columnRuleColor: val });
  };

  // PanelColorSettings コンポーネントを追加(PanelBody に含める必要はない)
  return (
    <>
      <InspectorControls>
        <PanelBody title="Column Settings">
          <RangeControl
            label="Columns"
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label="Width"
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label="Gap"
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody title="Column Separator" initialOpen={false}>
          <SelectControl
            label="Style"
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: "None",
                value: "none",
              },
              {
                label: "Solid",
                value: "solid",
              },
              {
                label: "Dotted",
                value: "dotted",
              },
              {
                label: "Dashed",
                value: "dashed",
              },
              {
                label: "Double",
                value: "double",
              },
              {
                label: "Groove",
                value: "groove",
              },
              {
                label: "Ridge",
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label="Width"
            onChange={onChangeColumnRuleWidth}
            value={columnRuleWidth}
            min={1}
            max={8}
          />
        </PanelBody>
        <PanelColorSettings
          title="Color settings"
          colorSettings={[
            {
              label: "Separator color",
              value: columnRuleColor,
              onChange: onChangeColumnRuleColor,
            },
          ]}
        ></PanelColorSettings>
      </InspectorControls>
      <RichText
        {...useBlockProps({ style: columnStyles })}
        tagName="p"
        onChange={onChangeContent}
        value={attributes.content}
        placeholder="Enter some text ..."
      />
    </>
  );
}

PanelColorSettings コンポーネントは、PanelBody コンポーネントと同様に title プロパティを取りますが、PanelBody コンポーネントのように子コンポーネントを持つ代わりに、オブジェクトの配列を取る colorSettings プロパティを持ちます。

colorSettings プロパティの配列内の各オブジェクトは、block.json で定義された属性 (この場合は columnRuleColor) から値を取得して value に設定し、onChange イベントにイベントハンドラ関数を割り当てます。また、それぞれに label プロパティでラベルを指定できます。

save.js では、属性 columnRuleColor を取得して columnStyles に追加します。

import { useBlockProps, RichText } from "@wordpress/block-editor";

  export default function save({ attributes }) {
  // attributes から columnRuleColor を分割代入で取得
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  } = attributes;
  // スタイルを表すオブジェクトに columnRuleColor を追加
  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  };

  return (
    <RichText.Content
      {...useBlockProps.save({ style: columnStyles })}
      tagName="p"
      value={attributes.content}
    />
  );
}

save.js を保存したら、エディターで投稿を再読み込みしてから投稿を更新し、バリデーションエラーが表示されたら「復旧を試みる」をクリックします。

インスペクターの「Color settings」パネルに「Separator color」の コントロールが表示されます。

「Separator color」をクリックするとカラーパネルが表示され、色を設定することができます。

InnerBlocks コンポーネント

現在 RichText コンポーネントを使って p 要素で出力しているブロックを、見出しや段落、画像などの他のブロックを含めることができるブロックにするため、代わりに InnerBlocks コンポーネントを使います。

参考:ネストしたブロック: InnerBlocks の使用

block.json

まず、block.json の content 属性(RichText コンポーネントのコンテンツ)は不要なので、attributes プロパティ内の次の行を削除します。

コンテンツは InnerBlocks コンポーネント内の子ブロックによって処理されます。

// block.json の以下を削除
"content": {
  "type": "string",
  "source": "html",
  "selector": "p"
},
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Example block scaffolded with Create Block tool.",
  "example": {},
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    }
  },
  "attributes": {
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "columnGap": {
      "type": "integer",
      "default": 40
    },
    "columnRuleStyle": {
      "type": "string",
      "default": "solid"
    },
    "columnRuleWidth": {
      "type": "integer",
      "default": 1
    },
    "columnRuleColor": {
      "type": "string",
      "default": "#b8b8b8"
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

edit.json

RichText コンポーネントはもう必要ないので、RichText コンポーネントのインポートを削除し、代わりに InnerBlocks コンポーネントをインポートします。

// RichText のインポートを削除し、代わりに InnerBlocks をインポート
import {
  useBlockProps,
  InnerBlocks,
  InspectorControls,
  PanelColorSettings,
} from "@wordpress/block-editor";

content 属性を削除したため、content 属性を更新する以下のイベントハンドラー onChangeContent も不要なので削除します。

// 以下の定義を削除
const onChangeContent = (val) => {
  setAttributes({ content: val });
};

次に、JSX から RichText コンポーネントを削除し、div 要素に置き換えます。

この div 要素はブロックのラッパー要素になるため、RichText コンポーネントの場合同様、useBlockProps をスプレッド演算子で展開し、useBlockProps() にパラメータとして style 属性を定義するオブジェクトを渡す必要があります。

そして div 要素は、新しくインポートされた InnerBlocks コンポーネントをラップします(133-135)。

import { __ } from "@wordpress/i18n";
// RichText のインポートを削除し、代わりに InnerBlocks をインポート
import {
  useBlockProps,
  InnerBlocks,
  InspectorControls,
  PanelColorSettings,
} from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  } = attributes;
  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  };
  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  const onChangeColumnRuleWidth = (val) => {
    setAttributes({ columnRuleWidth: Number(val) });
  };
  // 属性 columnRuleColor を更新するためのイベントハンドラーを追加
  const onChangeColumnRuleColor = (val) => {
    setAttributes({ columnRuleColor: val });
  };

  // PanelColorSettings コンポーネントを追加(PanelBody に含める必要はない)
  return (
    <>
      <InspectorControls>
        <PanelBody title="Column Settings">
          <RangeControl
            label="Columns"
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label="Width"
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label="Gap"
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody title="Column Separator" initialOpen={false}>
          <SelectControl
            label="Style"
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: "None",
                value: "none",
              },
              {
                label: "Solid",
                value: "solid",
              },
              {
                label: "Dotted",
                value: "dotted",
              },
              {
                label: "Dashed",
                value: "dashed",
              },
              {
                label: "Double",
                value: "double",
              },
              {
                label: "Groove",
                value: "groove",
              },
              {
                label: "Ridge",
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label="Width"
            onChange={onChangeColumnRuleWidth}
            value={columnRuleWidth}
            min={1}
            max={8}
          />
        </PanelBody>
        <PanelColorSettings
          title="Color settings"
          colorSettings={[
            {
              label: "Separator color",
              value: columnRuleColor,
              onChange: onChangeColumnRuleColor,
            },
          ]}
        ></PanelColorSettings>
      </InspectorControls>
      <div {...useBlockProps({ style: columnStyles })}>
        <InnerBlocks />
      </div>
    </>
  );
}

※ 単一のブロックは、1つの InnerBlocks コンポーネントのみを含むことがでます。

save.js

save.js を変更します(edit.js に加えた変更と似ています)。

不要になった RichText コンポーネントの代わりに InnerBlocks コンポーネントをインポートします。

そして、JSX の RichText コンポーネントを単純な div 要素に置き換えます。

この div 要素がブロックラッパー要素になるので、useBlockProps.save() をスプレッド演算子で展開し、useBlockProps.save() にパラメータとして style 属性を定義するオブジェクトを渡します。

そして div 要素で InnerBlocks コンポーネント(InnerBlocks.Content)をラップします。

※ save 関数では InnerBlocks.Content をレンダリングします(ネストされたブロックのコンテンツに自動的に置き換えられます)。

// RichText の代わりに InnerBlocks をインポート
import { useBlockProps, InnerBlocks } from "@wordpress/block-editor";

export default function save({ attributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  } = attributes;
  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  };

  // JSX の RichText を div 要素に置き換え useBlockProps.save() に style プロパティを渡して展開
  // InnerBlocks.Content をラップ
  return (
    <div {...useBlockProps.save({ style: columnStyles })}>
      <InnerBlocks.Content />
    </div>
  );
}

save.js を保存し、エディターでページをリロードします。バリデーションエラーが表示されたら「復旧を試みる」をクリックします。

コンテンツがクリアされ、Multi Columns ブロック内に任意のブロックを追加できます。

以下は見出しや段落、画像を子ブロックとして Multi Columns ブロックに追加した例です。

これらの子ブロックには、インスペクターパネルに独自のコントロールがあるため、それぞれについてフォントサイズや色、余白などを調整できます。

変更はフロントエンドにも反映されます。

allowedBlocks ブロックの許可(制限)

InnerBlocks には様々なブロックを含むことができますが、allowedBlocks プロパティを使用して、ユーザーが InnerBlocks コンポーネントに追加できるブロックを制限することができます。

allowedBlocks プロパティには文字列の配列を含めることができ、各文字列にはブロックの名前(識別子)を含める必要があります。allowedBlocks が設定されている場合、配列で指定されたブロックのみを挿入できます。※ ブロックの名前(識別子)は、Core Blocks Reference などで確認できます。

この例の場合、「新聞風」のブロックにしたいので、新聞記事に含まれる可能性のあるコンテンツの種類として、見出し、段落、画像のみを許可することにします(必要に応じて追加します)。

allowedBlocks プロパティに指定する配列を Edit() 関数の中で定義します。

任意の名前を付けることができますが、わかりやすくするために ALLOWED_BLOCKS とします。また、記述場所は return の前であればどこでも良いですが、const columnStyles の定義の後などに記述します。

// InnerBlocks に追加できるブロック(ユーザーに使用を許可するブロックの識別子の配列)
const ALLOWED_BLOCKS = [ 'core/heading', 'core/paragraph', 'core/image' ];

そして InnerBlocks コンポーネントの allowedBlocks プロパティの値として ALLOWED_BLOCKS を使用します。

<InnerBlocks allowedBlocks={ALLOWED_BLOCKS} />

edit.js への変更を保存し、エディターでブロックをリロードします。これで、Multi Columns ブロック内に内部ブロックとしてブロックを追加しようとすると、配列で定義されたブロックに制限されます。

import { __ } from "@wordpress/i18n";
import {
  useBlockProps,
  InnerBlocks,
  InspectorControls,
  PanelColorSettings,
} from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  } = attributes;
  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  };
  // InnerBlocks に追加できるブロックのリスト(allowedBlocks プロパティに指定する配列)
  const ALLOWED_BLOCKS = ["core/heading", "core/paragraph", "core/image"];

  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  const onChangeColumnRuleWidth = (val) => {
    setAttributes({ columnRuleWidth: Number(val) });
  };
  const onChangeColumnRuleColor = (val) => {
    setAttributes({ columnRuleColor: val });
  };

  return (
    <>
      <InspectorControls>
        <PanelBody title="Column Settings">
          <RangeControl
            label="Columns"
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label="Width"
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label="Gap"
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody title="Column Separator" initialOpen={false}>
          <SelectControl
            label="Style"
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: "None",
                value: "none",
              },
              {
                label: "Solid",
                value: "solid",
              },
              {
                label: "Dotted",
                value: "dotted",
              },
              {
                label: "Dashed",
                value: "dashed",
              },
              {
                label: "Double",
                value: "double",
              },
              {
                label: "Groove",
                value: "groove",
              },
              {
                label: "Ridge",
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label="Width"
            onChange={onChangeColumnRuleWidth}
            value={columnRuleWidth}
            min={1}
            max={8}
          />
        </PanelBody>
        <PanelColorSettings
          title="Color settings"
          colorSettings={[
            {
              label: "Separator color",
              value: columnRuleColor,
              onChange: onChangeColumnRuleColor,
            },
          ]}
        ></PanelColorSettings>
      </InspectorControls>
      <div {...useBlockProps({ style: columnStyles })}>
        <InnerBlocks allowedBlocks={ALLOWED_BLOCKS} />
      </div>
    </>
  );
}

利用可能な親ブロックを指定するには、block.json の parent プロパティを使用します。

template プロパティ

現時点で、作成中の Multi Columns ブロックを新規に挿入すると、以下のように表示され、何のためのブロックなのか、ブロックで何ができるかをユーザーに示すものがほとんどありません。

InnerBlocks コンポーネントの template プロパティを使うと、デフォルトの初期状態(デモコンテンツのようなもの)を指定することができます。

template プロパティは、配列を値として受け取ります。この配列には、テンプレートに含めるブロックの名前(識別子)と、placeholder プロパティなど(見出しの場合は、level プロパティも必要)を持つオブジェクト(block attributes)を受け取る配列が含まれます。

この例では、temmplate プロパティに指定する MC_TEMPLATE という名前の配列を定義します。

const MC_TEMPLATE = [
  // [ 'ブロックの識別子'、{ オブジェクト(block attributes) } ]
  [ 'core/heading', { level: 2, placeholder: 'Heading...' } ],
  [ 'core/paragraph', { placeholder: 'Paragraph...' } ],
  [ 'core/heading', { level: 3, placeholder: 'Sub-heading...' } ],
  [ 'core/paragraph', { placeholder: 'Paragraph...' } ],
];

そして定義した MC_TEMPLATE を InnerBlocks コンポーネントの temmplate プロパティに指定します。

<InnerBlocks allowedBlocks={ALLOWED_BLOCKS} template={MC_TEMPLATE} />

上記により、新規に Multi Columns ブロックを挿入すると、以下のように表示されます。

import { __ } from "@wordpress/i18n";
import {
  useBlockProps,
  InnerBlocks,
  InspectorControls,
  PanelColorSettings,
} from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  } = attributes;
  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  };
  // InnerBlocks に追加できるブロックのリスト(allowedBlocks プロパティに指定する配列)
  const ALLOWED_BLOCKS = ["core/heading", "core/paragraph", "core/image"];

  // InnerBlocks temmplate プロパティに指定する配列
  const MC_TEMPLATE = [
    // [ 'ブロックの識別子'、{ オブジェクト } ]
    [ 'core/heading', { level: 2, placeholder: 'Heading...' } ],
    [ 'core/paragraph', { placeholder: 'Paragraph...' } ],
    [ 'core/heading', { level: 3, placeholder: 'Sub-heading...' } ],
    [ 'core/paragraph', { placeholder: 'Paragraph...' } ],
  ];

  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  const onChangeColumnRuleWidth = (val) => {
    setAttributes({ columnRuleWidth: Number(val) });
  };
  const onChangeColumnRuleColor = (val) => {
    setAttributes({ columnRuleColor: val });
  };

  return (
    <>
      <InspectorControls>
        <PanelBody title="Column Settings">
          <RangeControl
            label="Columns"
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label="Width"
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label="Gap"
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody title="Column Separator" initialOpen={false}>
          <SelectControl
            label="Style"
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: "None",
                value: "none",
              },
              {
                label: "Solid",
                value: "solid",
              },
              {
                label: "Dotted",
                value: "dotted",
              },
              {
                label: "Dashed",
                value: "dashed",
              },
              {
                label: "Double",
                value: "double",
              },
              {
                label: "Groove",
                value: "groove",
              },
              {
                label: "Ridge",
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label="Width"
            onChange={onChangeColumnRuleWidth}
            value={columnRuleWidth}
            min={1}
            max={8}
          />
        </PanelBody>
        <PanelColorSettings
          title="Color settings"
          colorSettings={[
            {
              label: "Separator color",
              value: columnRuleColor,
              onChange: onChangeColumnRuleColor,
            },
          ]}
        ></PanelColorSettings>
      </InspectorControls>
      <div {...useBlockProps({ style: columnStyles })}>
        <InnerBlocks allowedBlocks={ALLOWED_BLOCKS}  template={MC_TEMPLATE} />
      </div>
    </>
  );
}

以下は、paragraph の placeholder プロパティに指定する文字列を別途定義する例です。

TEMPLATE_PARAGRAPHS など、意味のある変数名を付けて、以下のような文字列の配列を用意します。

const TEMPLATE_PARAGRAPHS = [
  'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt nec tortor in laoreet. Fusce blandit augue vel felis pulvinar, a gravida nibh maximus. Morbi imperdiet mattis sapien, in dignissim enim vehicula at. Fusce egestas sed dui et feugiat. In hac habitasse platea dictumst. Proin sed convallis turpis, a dictum metus. ',
  'Nullam a nisi blandit, aliquet felis condimentum, efficitur dui. Maecenas sed efficitur magna. Praesent vel quam tristique velit facilisis laoreet. Integer ac leo eu enim mattis aliquet sed quis lacus. Nunc at ultrices quam. Vestibulum sit amet metus nec enim dapibus ullamcorper non eu quam. Duis mattis interdum scelerisque. Pellentesque eu lobortis nulla, quis laoreet lacus.',
];

temmplate プロパティに指定する MC_TEMPLATE の中で上記の文字列を参照するようにします。

const MC_TEMPLATE = [
  ["core/heading", { level: 2, placeholder: "Heading..." }],
  // 定義した文字列を TEMPLATE_PARAGRAPHS[0] で参照
  ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[0] }],
  ["core/heading", { level: 3, placeholder: "Sub-heading..." }],
  // 定義した文字列を TEMPLATE_PARAGRAPHS[1] で参照
  ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[1] }],
];

この場合、新規に Multi Columns ブロックを挿入すると、以下のように表示されます。

import { __ } from "@wordpress/i18n";
import {
  useBlockProps,
  InnerBlocks,
  InspectorControls,
  PanelColorSettings,
} from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  } = attributes;
  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  };

  const ALLOWED_BLOCKS = ["core/heading", "core/paragraph", "core/image"];

  // temmplate プロパティの paragraph の placeholder プロパティに指定する文字列を別途定義
  const TEMPLATE_PARAGRAPHS = [
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt nec tortor in laoreet. Fusce blandit augue vel felis pulvinar, a gravida nibh maximus. Morbi imperdiet mattis sapien, in dignissim enim vehicula at. Fusce egestas sed dui et feugiat. In hac habitasse platea dictumst. Proin sed convallis turpis, a dictum metus. ",
    "Nullam a nisi blandit, aliquet felis condimentum, efficitur dui. Maecenas sed efficitur magna. Praesent vel quam tristique velit facilisis laoreet. Integer ac leo eu enim mattis aliquet sed quis lacus. Nunc at ultrices quam. Vestibulum sit amet metus nec enim dapibus ullamcorper non eu quam. Duis mattis interdum scelerisque. Pellentesque eu lobortis nulla, quis laoreet lacus.",
  ];
  // InnerBlocks temmplate プロパティに指定する配列
  const MC_TEMPLATE = [
    ["core/heading", { level: 2, placeholder: "Heading..." }],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[0] }],
    ["core/heading", { level: 3, placeholder: "Sub-heading..." }],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[1] }],
  ];

  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  const onChangeColumnRuleWidth = (val) => {
    setAttributes({ columnRuleWidth: Number(val) });
  };
  const onChangeColumnRuleColor = (val) => {
    setAttributes({ columnRuleColor: val });
  };

  return (
    <>
      <InspectorControls>
        <PanelBody title="Column Settings">
          <RangeControl
            label="Columns"
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label="Width"
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label="Gap"
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody title="Column Separator" initialOpen={false}>
          <SelectControl
            label="Style"
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: "None",
                value: "none",
              },
              {
                label: "Solid",
                value: "solid",
              },
              {
                label: "Dotted",
                value: "dotted",
              },
              {
                label: "Dashed",
                value: "dashed",
              },
              {
                label: "Double",
                value: "double",
              },
              {
                label: "Groove",
                value: "groove",
              },
              {
                label: "Ridge",
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label="Width"
            onChange={onChangeColumnRuleWidth}
            value={columnRuleWidth}
            min={1}
            max={8}
          />
        </PanelBody>
        <PanelColorSettings
          title="Color settings"
          colorSettings={[
            {
              label: "Separator color",
              value: columnRuleColor,
              onChange: onChangeColumnRuleColor,
            },
          ]}
        ></PanelColorSettings>
      </InspectorControls>
      <div {...useBlockProps({ style: columnStyles })}>
        <InnerBlocks allowedBlocks={ALLOWED_BLOCKS} template={MC_TEMPLATE} />
      </div>
    </>
  );
}
Block attributes

template で定義できるすべてのブロック属性のリストを見つけるには、ブロックの block.json ファイルを参照し、属性とサポート値を確認します(Block attributes)。

例えば、見出し(heading)の block.json を確認すると、ブロックに level 属性があり、anchor パラメータをサポートしていることがわかります。

ブロックのリスト:gutenberg/packages/block-library/src/

ローカルの wp-includes/blocks/heading/block.json でも確認できます。

また、Core Blocks Reference の各ブロックの Attributes に属性がリストされています。詳細は (Source) をクリックして、block.json を確認できます。

例えば、前述の temmplate プロパティに指定する配列(MC_TEMPLATE)を以下のように書き換えると(placeholder の代わりに content を指定し、textAlign や dropCap を指定)、

/* 確認用(後でもとに戻します) */
const MC_TEMPLATE = [
  // placeholder を content に変更し、textAlign を指定
  ["core/heading", { level: 2, content: "News", textAlign: "right" }],
  // placeholder を content に変更し、dropCap を指定
  ["core/paragraph", { content: TEMPLATE_PARAGRAPHS[0], dropCap: "true" }],
  ["core/heading", { level: 3, placeholder: "Sub-heading..." }],
  ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[1] }],
];

新規にブロックを挿入すると、以下のように表示されます。

プロジェクトの改良

ベストプラクティスに従って、ブロックがより完成度の高いものになるようにいくつかの改良を加えます。

ブロックを検索可能に

ブロックを検索可能にします。現時点では、ユーザーがブロックを見つけるには、ブロックの正確な名前を知る必要があります。ブロックが提供する機能を備えたブロックを簡単に検索できるように、ユーザーが検索すると思われる用語の配列を含む keywords プロパティを block.json に追加します。

 "keywords": ["newspaper", "columns", "text"],

例えば、上記を block.json に追加して「news」と検索すると Multi Columns ブロックが候補として表示されます。

コンテンツ幅

テーマでは標準のコンテンツ幅が定義されています。例えば Twenty Twenty-Five の場合、コンテンツ幅は 645px に設定されているため、最小列幅が 120 ピクセル、最小ギャップが 10 ピクセル、パディングが 0 ピクセルであっても、5 列分のスペースしかありません。

これを解決するには、block.json の supports セクションに align プロパティを追加します。このプロパティは、文字列「left、center、right、wide、full」を含む配列を値として受け取ります。

この例では supports.align に「wide」と「full」を指定して、「幅広」と「全幅」のオプションを表示できるようにします。

"supports": {
  "html": true,
  "color": {},
  "spacing": {
    "margin": true,
    "padding": true,
    "blockGap": true
  },
  "align": ["wide", "full"]
},

block.json を保存し、エディターでブロックを再読み込みすると、ブロックツールバーに「配置」のアイコンが追加され、「幅広」と「全幅」、及びデフォルトの「なし」のオプションが選択できるようになります。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Custom Multi Columns block for newspaper style.",
  "keywords": ["newspaper", "columns", "text"],
  "example": {},
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    },
    "align": ["wide", "full"]
  },
  "attributes": {
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "columnGap": {
      "type": "integer",
      "default": 40
    },
    "columnRuleStyle": {
      "type": "string",
      "default": "solid"
    },
    "columnRuleWidth": {
      "type": "integer",
      "default": 1
    },
    "columnRuleColor": {
      "type": "string",
      "default": "#b8b8b8"
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

block.json parent プロパティ

block.json の parent を設定すると、ブロックは指定したブロック内にネストされる場合のみ利用可能になります(利用可能な親ブロックを指定できます)。

現時点ではコアのカラムブロックなど任意のブロックの中に Multi Columns ブロックを挿入することができてしまいますが、これは望ましい使い方ではありません。

例えば、投稿にカラムブロックを挿入して、

2カラムのレイアウトを選択し、

1つのカラムに Multi Columns ブロックを挿入することができます。

この場合、以下のような表示になってしまい、意図した使い方ではありません。

block.json で parent プロパティを定義することで、ユーザーが上記のようなことを実行できないようにすることができます。

parent プロパティは、ブロックに許可されている親の配列を受け取ります。

以下を block.json に記述すると、Multi Columns ブロックは投稿コンテンツのルートレベルにのみ挿入でき、カラムブロックやグループブロックなどの別のブロック内には追加できないようにします。

"parent": ["core/post-content"],

上記を記述し、ファイルを保存すると、Multi Columns ブロックは投稿コンテンツのルートレベルにのみ追加でき、他のブロックの子として追加することはできないようになります。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Custom Multi Columns block for newspaper style.",
  "keywords": ["newspaper", "columns", "text"],
  "example": {},
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    },
    "align": ["wide", "full"]
  },
  "attributes": {
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "columnGap": {
      "type": "integer",
      "default": 40
    },
    "columnRuleStyle": {
      "type": "string",
      "default": "solid"
    },
    "columnRuleWidth": {
      "type": "integer",
      "default": 1
    },
    "columnRuleColor": {
      "type": "string",
      "default": "#b8b8b8"
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "parent": ["core/post-content"],
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

国際化

国際化internationalization)とは、他の言語に簡単に翻訳できるようなコードを開発するプロセスです。国際化は、多くの場合、i18n と略されます (i と n の間に 18 文字あるため)。

国際化に関するベストプラクティスに従うには、プラグインを翻訳可能にする必要があります。

PanelBody の見出し(title プロパティ)やコントロールのラベル(label プロパティ)などに指定するテキストはインターフェイスの一部としてエディターに表示されるため、関数 __(2つのアンダースコア)を使って翻訳可能にします。

__() は翻訳テキストを取得する関数です。

この関数を利用するには i18n パッケージから関数をインポートする必要がありますが、create-block コマンドで生成した edit.js には以下のようにすでにインポートが記述されています。

import { __ } from "@wordpress/i18n";

この関数は翻訳する文字列とテキストドメインの2つのパラメーターを取ります。以下が書式です。

__( '翻訳する文字列', 'テキストドメイン' );

テキストドメインは、block.json の textdomain プロパティの値で、create-block コマンドでひな型を生成する際に自動的に設定されています。

この例の場合、textdomain プロパティの値は npx @wordpress/create-block コマンドを実行したときに指定したプロジェクト名(slug)になるので「multi-columns」になります。

現在、PanelBody の title プロパティの値は以下のように "Column Settings" となっています。

<PanelBody title="Column Settings">

文字列 "Column Settings" を翻訳可能とするには、この文字列を __() 関数の最初のパラメータに渡し、2番目のパラメータにテキストドメインを渡します。

 __( 'Column Settings', 'multi-columns' );

JSX で属性として JavaScript 式を埋め込むには中括弧 {} を使用する必要があるので、PanelBody コンポーネント(JSX)の記述は以下のようになります。

<PanelBody title={ __( 'Column Settings', 'multi-columns' ) }>

以下は RangeControl と NumberControl の label プロパティも __() 関数で翻訳可能にした例です。

<PanelBody title={ __( 'Column Settings', 'multi-columns' ) }>
  <RangeControl
    label={ __( 'Columns', 'multi-columns' ) }
    value={columnCount}
    onChange={onChangeColumnCount}
    min={2}
    max={6}
  />
  <NumberControl
    label={ __( 'Width', 'multi-columns' ) }
    value={columnWidth}
    onChange={onChangeColumnWidth}
    min={120}
    max={500}
    step={10}
  />
  <NumberControl
    label={ __( 'Gap', 'multi-columns' ) }
    onChange={onChangeColumnGap}
    value={columnGap}
    min={10}
    max={100}
  />
</PanelBody>

※ 一部の文字列には中括弧は必要ありません。例えば、SelectControl オプションの label は配列内のオブジェクトであり、配列自体はすでに中括弧で囲まれているため、次のように記述します。

options={[
  {
    label: __( 'None', 'multi-columns' ),
    value: "none",
  },
  {
    label: __( 'Solid', 'multi-columns' ),
    value: "solid",
  },
  {
    label: __( 'Dotted', 'multi-columns' ),
    value: "dotted",
  },
  {
    label: __( 'Dashed', 'multi-columns' ),
    value: "dashed",
  },
  {
    label: __( 'Double', 'multi-columns' ),
    value: "double",
  },
  {
    label: __( 'Groove', 'multi-columns' ),
    value: "groove",
  },
  {
    label: __( 'Ridge', 'multi-columns' ),
    value: "ridge",
  },
]}

edit.js のコードのインターフェイスに表示されるすべての文字列を翻訳可能に変更します。この例の save.js にはテキスト文字列がないため、変更が必要なのは edit.js のみです。

※ この例では、後から文字列を翻訳可能にしましたが、実際の制作では、最初から文字列をコードに追加するときに翻訳可能にします。

関連項目:翻訳を作成

import { __ } from "@wordpress/i18n";
import { useBlockProps, InnerBlocks, InspectorControls, PanelColorSettings } from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  } = attributes;
  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  };

  const ALLOWED_BLOCKS = ["core/heading", "core/paragraph", "core/image"];

  const TEMPLATE_PARAGRAPHS = [
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt nec tortor in laoreet. Fusce blandit augue vel felis pulvinar, a gravida nibh maximus. Morbi imperdiet mattis sapien, in dignissim enim vehicula at. Fusce egestas sed dui et feugiat. In hac habitasse platea dictumst. Proin sed convallis turpis, a dictum metus. ",
    "Nullam a nisi blandit, aliquet felis condimentum, efficitur dui. Maecenas sed efficitur magna. Praesent vel quam tristique velit facilisis laoreet. Integer ac leo eu enim mattis aliquet sed quis lacus. Nunc at ultrices quam. Vestibulum sit amet metus nec enim dapibus ullamcorper non eu quam. Duis mattis interdum scelerisque. Pellentesque eu lobortis nulla, quis laoreet lacus.",
  ];

  const MC_TEMPLATE = [
    ["core/heading", { level: 2, placeholder:  __( 'Heading...', 'multi-columns' )  }],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[0] }],
    ["core/heading", { level: 3, placeholder: __( 'Sub-heading...', 'multi-columns' )  }],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[1] }],
  ];

  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  const onChangeColumnRuleWidth = (val) => {
    setAttributes({ columnRuleWidth: Number(val) });
  };
  const onChangeColumnRuleColor = (val) => {
    setAttributes({ columnRuleColor: val });
  };

  return (
    <>
      <InspectorControls>
      <PanelBody title={ __( 'Column Settings', 'multi-columns' ) }>
          <RangeControl
            label={ __( 'Columns', 'multi-columns' ) }
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
          label={ __( 'Width', 'multi-columns' ) }
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label={ __( 'Gap', 'multi-columns' ) }
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody title={ __( 'Column Separator', 'multi-columns' ) } initialOpen={false}>
          <SelectControl
            label={ __( 'Style', 'multi-columns' ) }
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: __( 'None', 'multi-columns' ),
                value: "none",
              },
              {
                label: __( 'Solid', 'multi-columns' ),
                value: "solid",
              },
              {
                label: __( 'Dotted', 'multi-columns' ),
                value: "dotted",
              },
              {
                label: __( 'Dashed', 'multi-columns' ),
                value: "dashed",
              },
              {
                label: __( 'Double', 'multi-columns' ),
                value: "double",
              },
              {
                label: __( 'Groove', 'multi-columns' ),
                value: "groove",
              },
              {
                label: __( 'Ridge', 'multi-columns' ),
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label={ __( 'Width', 'multi-columns' ) }
            onChange={onChangeColumnRuleWidth}
            value={columnRuleWidth}
            min={1}
            max={8}
          />
        </PanelBody>
        <PanelColorSettings
          title={ __( 'Color settings', 'multi-columns' ) }
          colorSettings={[
            {
              label: __( 'Separator color', 'multi-columns' ),
              value: columnRuleColor,
              onChange: onChangeColumnRuleColor,
            },
          ]}
        ></PanelColorSettings>
      </InspectorControls>
      <div {...useBlockProps({ style: columnStyles })}>
        <InnerBlocks allowedBlocks={ALLOWED_BLOCKS} template={MC_TEMPLATE} />
      </div>
    </>
  );
}
import { useBlockProps, InnerBlocks } from "@wordpress/block-editor";

export default function save({ attributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  } = attributes;
  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
  };

  return (
    <div {...useBlockProps.save({ style: columnStyles })}>
      <InnerBlocks.Content />
    </div>
  );
}

Bottom margin styles are deprecated

WordPress 6.7 リリースでは、新しいマージンフリースタイルをまだオプトインしていない以下のコンポーネントについて、非推奨の警告のログがコンソールに出力されるようになっています。

  • BaseControl
  • CheckboxControl
  • ComboboxControl
  • DimensionControl
  • FocalPointPicker
  • FormTokenField
  • RangeControl
  • SearchControl
  • SelectControl
  • TextControl
  • TextareaControl
  • ToggleControl
  • ToggleGroupControl
  • TreeSelect

エディターでブロックを選択してコンソールを確認すると、以下のように警告が表示されます。

これは上記のコンポーネントに下部マージンを提供するスタイルが付属しているためですが、将来的(WordPress 7)にマージンが完全に削除されので、そのための警告です。

コントロールのマージンを調整する必要がない場合は、特に気にする必要はないと思いますが、マージンを削除したスタイルを適用して警告を非表示にするには、以下のように __nextHasNoMarginBottom プロパティを true に設定します(7行目)。

<RangeControl
  label={ __( 'Columns', 'multi-columns' ) }
  value={columnCount}
  onChange={onChangeColumnCount}
  min={2}
  max={6}
  __nextHasNoMarginBottom={ true }
/>

この例では、このオプションは指定していません。

詳細:Updates to user-interface components in WordPress 6.7

ブロックスタイル

ブロックスタイル (Block Styles) を使用すると、ブロックに代替のスタイル(バリエーション)を追加することができ、ユーザーはブロックのスタイルを選択することができます。

ブロックスタイルはブロックスタイルバリエーションとも呼びます(同じです)。

ブロックスタイルの追加

この例では、これまで作業してきたデフォルトのスタイル(Default)と、ブロック内の記事の最初の文字を大きくするスタイル (Drop-Cap) の 2 つのブロックスタイルのバリエーションを実装します。

必要に応じて任意の数のバリエーション(ブロックスタイル)を提供することができます。

以下はブロックスタイルを追加する大まかな手順です。

  1. block.json で styles プロパティを使ってブロックスタイルのバリエーションを定義
  2. 各ブロックスタイルの CSS を style.scss に追加
  3. プレビューを作成

block.json にブロックスタイルを定義

block.json に styles プロパティを追加します。このプロパティは、ブロックスタイルを定義するオブジェクトの配列を値として受け取ります。

"styles": [
  {
    "name": "default",
    "label": "Default",
    "isDefault": true
  },
  {
    "name": "drop-cap",
    "label": "Drop-Cap"
  }
],

配列内の各オブジェクトには、name プロパティと label プロパティがあります。name プロパティはクラスを生成するために使用される一意の名前で、label はインスペクターパネルに表示されるものです。

そのスタイルをブロックのデフォルトとする場合は isDefault プロパティに true を指定します。

block.json を保存してエディターで確認すると、ブロックのインスペクターパネルの「スタイル」タブで定義した各スタイルを選択できるようになります。

但し、まだスタイルを定義していないので、スタイルを選択しても何も変化はありません。

style.scss にブロックスタイルの CSS を追加

「スタイル」タブでブロックスタイルを選択すると、WordPress はブロックにクラスを追加します。

クラスの形式は is-style-{name} で、{name} は block.json で定義したブロックスタイルの name プロパティに指定した値です。

この例の場合、ブロックには次の2つのクラスのいずれかが追加されます。

  • is-style-default
  • is-style-drop-cap

「スタイル」タブで定義したスタイルを切り替えて、

「設定」タブで「高度な設定」の「追加 CSS クラス」を確認すると、これらのクラスが適用されていることがわかります。

「Default」はこれまで使用してきたスタイルなので追加のスタイルは必要ありませんが、「Drop-Cap」のスタイルを WordPress によって追加されるクラス is-style-drop-cap を使って style.scss に追加します。

.wp-block-create-block-multi-columns {
  box-sizing: border-box;
  column-fill: balance;

  /* Drop-Cap(.is-style-drop-cap)のスタイルを追加 */
  &.is-style-drop-cap {
    p:first-of-type:first-letter {
      color: #b8b8b8;
      float: left;
      font-size: 3.8rem;
      line-height: 3.5rem;
      padding-right: 0.33rem;
    }
  }
}

style.css を保存すると、エディターで各バリエーションを選択してブロックの変更を確認できます。投稿を保存すると、フロントエンドにスタイルが反映されることも確認できます。

但し、スタイルのバリエーションを選択するためにボタンの上にマウスを移動しても、プレビューのコンテンツが表示されません。

プレビューを作成

スタイルのバリエーションのプレビューを作成するには、プレビューのテンプレートを定義します。

block.json の example プロパティには、create-block コマンドで生成された際に空のオブジェクト {} が設定されていますが、その中に innerBlocks プロパティを追加します。

"example": {
  "innerBlocks": []
},

innerBlocks プロパティは配列を受け取り、この配列にはプレビューに表示されるブロック(コンテンツ)を定義するオブジェクトを設定します。

配列内の各オブジェクトには、ブロックの名前を値として持つ name プロパティと、そのブロックの属性を含むオブジェクトとなる attribute プロパティを設定します。

ここでは、確認用に name プロパティに段落ブロック(core/paragraph)を指定し、attribute プロパティに content 属性(段落のコンテンツ)を指定して、innerBlocks 配列に段落を定義するオブジェクトを1つ配置します。

"example": {
  "innerBlocks": [
    {
      "name": "core/paragraph",
      "attributes": {
        "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit..."
      }
    }
  ]
}

block.json を保存し、ブラウザでエディターを再読み込みすると、上記で設定した段落がプレビューのコンテンツとして表示されます。

見出しなど他のコンテンツを定義するオブジェクトを example.innerBlocks プロパティに追加します。

innerBlocks.attributes に設定できる属性は InnerBlocks コンポーネントの template プロパティで定義できるブロック属性と同様、Block attributes で確認できます。

"example": {
  "innerBlocks": [
    {
      "name": "core/heading",
      "attributes": {
        "level": 3,
        "content": "Heading"
      }
    },
    {
      "name": "core/paragraph",
      "attributes": {
        "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit..."
      }
    },
    {
      "name": "core/heading",
      "attributes": {
        "level": 4,
        "content": "Sub-heading"
      }
    },
    {
      "name": "core/paragraph",
      "attributes": {
        "content": "Interdum et malesuada fames ac ante ipsum primis in faucibus...."
      }
    }
  ]
},

block.json を保存し、ブラウザでエディターを再読み込みすると、上記で設定した見出しなどがプレビューのコンテンツとして表示されます。

但し、1列で表示され、このブロックが複数のカラムで構成されることがプレビューでわかりません。

example プロパティには、attributes プロパティ(example.attributes)を設定することができます。

example.attributes にはトップレベルの attributes プロパティに定義された属性を含むオブジェクトを設定できます。この例では以下のように columnCount と columnWidth を設定します。

"example": {
  "innerBlocks": [
    {
      "name": "core/heading",
      "attributes": {
        "level": 3,
        "content": "Heading"
      }
    },
    {
      "name": "core/paragraph",
      "attributes": {
        "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin finibus, lectus non interdum cursus, arcu sapien mollis lacus, et tincidunt odio nisi ut purus."
      }
    },
    {
      "name": "core/heading",
      "attributes": {
        "level": 4,
        "content": "Sub-heading"
      }
    },
    {
      "name": "core/paragraph",
      "attributes": {
        "content": "Interdum et malesuada fames ac ante ipsum primis in faucibus. Cras vestibulum mauris diam. "
      }
    }
  ],
  "attributes": {
    "columnCount": 2,
    "columnWidth": 80
  }
},

block.json を保存し、エディターを再読み込みするとプレビューのコンテンツが2列で表示されます。

example プロパティには、viewportWidth プロパティを使用してプレビューコンテナの幅をピクセル単位で定義することもできます。

以下は example プロパティに viewportWidth プロパティを追加し、attributes.columnCount を3に変更してプレビューを3列で表示する例です。

"example": {
  "innerBlocks": [
    {
      "name": "core/heading",
      "attributes": {
        "level": 3,
        "content": "Heading"
      }
    },
    {
      "name": "core/paragraph",
      "attributes": {
        "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin finibus, lectus non interdum cursus, arcu sapien mollis lacus, et tincidunt odio nisi ut purus."
      }
    },
    {
      "name": "core/heading",
      "attributes": {
        "level": 4,
        "content": "Sub-heading"
      }
    },
    {
      "name": "core/paragraph",
      "attributes": {
        "content": "Interdum et malesuada fames ac ante ipsum primis in faucibus. Cras vestibulum mauris diam. "
      }
    }
  ],
  "attributes": {
    "columnCount": 3,
    "columnWidth": 80
  },
  "viewportWidth": 720
},

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Custom Multi Columns block for newspaper style.",
  "keywords": ["newspaper", "columns", "text"],
  "example": {
    "innerBlocks": [
      {
        "name": "core/heading",
        "attributes": {
          "level": 3,
          "content": "Heading"
        }
      },
      {
        "name": "core/paragraph",
        "attributes": {
          "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin finibus, lectus non interdum cursus, arcu sapien mollis lacus, et tincidunt odio nisi ut purus. Duis eleifend, magna placerat faucibus tincidunt."
        }
      },
      {
        "name": "core/heading",
        "attributes": {
          "level": 4,
          "content": "Sub-heading"
        }
      },
      {
        "name": "core/paragraph",
        "attributes": {
          "content": "Interdum et malesuada fames ac ante ipsum primis in faucibus. Cras vestibulum mauris diam. Praesent semper diam a efficitur iaculis. Nullam lacinia augue quis lorem accumsan tempus."
        }
      }
    ],
    "attributes": {
      "columnCount": 3,
      "columnWidth": 80
    },
    "viewportWidth": 720
  },
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    },
    "align": ["wide", "full"]
  },
  "attributes": {
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "columnGap": {
      "type": "integer",
      "default": 40
    },
    "columnRuleStyle": {
      "type": "string",
      "default": "solid"
    },
    "columnRuleWidth": {
      "type": "integer",
      "default": 1
    },
    "columnRuleColor": {
      "type": "string",
      "default": "#b8b8b8"
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "parent": ["core/post-content"],
  "styles": [
    {
      "name": "default",
      "label": "Default",
      "isDefault": true
    },
    {
      "name": "drop-cap",
      "label": "Drop-Cap"
    }
  ],
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

設定オプションを追加(CSS 変数の利用)

ドロップキャップ(Drop-Cap)の色とサイズを変更するオプションのコントロールを追加します。

色の設定オプションを追加

色の設定を行うためのコントロールは、すでに実装している PanelColorSettings を利用します。

必要なデータは、区切り線の色(columnRuleColor)のオプションの追加と同様、色の値を格納する必要があるため、文字列型にします。

block.json の attributes に dropCapColor という新しい属性を追加し、この属性に Drop-Cap の色を保持します。文字列型のデータが必要なので type に string を設定し、任意の色のデフォルト値(以下では #b8b8b8)を default に設定します。

"dropCapColor": {
  "type": "string",
  "default": "#b8b8b8"
},

edit.js の Edit() 関数で、上記で設定した dropCapColor を attributes から分割代入で取得します。

const {
  columnCount,
  columnWidth,
  columnGap,
  columnRuleStyle,
  columnRuleWidth,
  columnRuleColor,
  dropCapColor,
} = attributes;

続いて(useBlockProps によってブロックラッパーに展開される)スタイルを定義する columnStyles オブジェクトに Drop-Cap の色を設定するプロパティを追加します。

但し、これまでの例では columnCount や columnRuleColor のように属性名が CSS のプロパティ名(column-count や column-rule-color)に対応していたのに対し、属性 dropCapColor は CSS のプロパティ名に対応していないので、オブジェクトの短縮構文は使いません。

また、columnStyles オブジェクトの他のすべてのスタイルはブロック全体に適用されますが、この場合は最初の段落の最初の文字のみをターゲットにするので、p:first-of-type:first-letter 疑似要素をターゲットにする必要があります。

この場合、CSS カスタムプロパティ(CSS 変数) を使用するのが簡単です。

これまでのように短縮構文を使用して属性を単純に追加するのではなく、以下のように CSS カスタムプロパティ(CSS 変数)を定義して、属性の値を割り当てます。

CSS カスタムプロパティには先頭にハイフンを2つ (--) 付けて好きな名前を付けることができますが、以下の --drop-cap-color のような意味のあるものを使用するのが無難です。

const columnStyles = {
  columnCount,
  columnWidth,
  columnGap,
  columnRuleStyle,
  columnRuleWidth,
  columnRuleColor,
  '--drop-cap-color': dropCapColor,
};

そして属性 dropCapColor を更新するためのイベントハンドラーを定義します。

const onChangeDropCapColor = (val) => {
  setAttributes({ dropCapColor: val });
};

PanelColorSettings コンポーネントの colorSettings 配列に Drop-Cap の色の設定を追加します。

value は attributes から分割代入で取得した dropCapColor を指定し、onChange には上記のイベントハンドラーを指定します。

<PanelColorSettings
  title={__("Color settings", "multi-columns")}
  colorSettings={[
    {
      label: __("Separator color", "multi-columns"),
      value: columnRuleColor,
      onChange: onChangeColumnRuleColor,
    },
    {
      label: __("Drop-Cap color", "multi-columns"),
      value: dropCapColor,
      onChange: onChangeDropCapColor,
    },
  ]}
></PanelColorSettings>

インスペクターの「Color settings」パネルに「Drop-Cap color」の コントロールが表示されます。

但し、色を変更してもドロップキャップの色は変わりません。

これは、現在色を変更しても、--drop-cap-color: #FFEE58(選択した色の値); のように CSS カスタムプロパティ --drop-cap-color の値が更新されるだけで、ドロップキャップのセレクタ p:first-of-type:first-letter の color の値はまだハードコードされた固定値のためです。

style.scss の p:first-of-type:first-letter の color の値に CSS カスタムプロパティ --drop-cap-color の値を var() 関数を使って指定します。

.wp-block-create-block-multi-columns {
  box-sizing: border-box;
  column-fill: balance;

  &.is-style-drop-cap {
    p:first-of-type:first-letter {
      color: var(--drop-cap-color);
      float: left;
      font-size: 3.8rem;
      line-height: 3.5rem;
      padding-right: 0.33rem;
    }
  }
}

これで「Drop-Cap color」の コントロールで設定した色がドロップキャップに反映されます。

import { __ } from "@wordpress/i18n";
import {
  useBlockProps,
  InnerBlocks,
  InspectorControls,
  PanelColorSettings,
} from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    dropCapColor,
  } = attributes;

  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    '--drop-cap-color': dropCapColor,
  };

  const ALLOWED_BLOCKS = ["core/heading", "core/paragraph", "core/image"];

  const TEMPLATE_PARAGRAPHS = [
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt nec tortor in laoreet. Fusce blandit augue vel felis pulvinar, a gravida nibh maximus. Morbi imperdiet mattis sapien, in dignissim enim vehicula at. Fusce egestas sed dui et feugiat. In hac habitasse platea dictumst. Proin sed convallis turpis, a dictum metus. ",
    "Nullam a nisi blandit, aliquet felis condimentum, efficitur dui. Maecenas sed efficitur magna. Praesent vel quam tristique velit facilisis laoreet. Integer ac leo eu enim mattis aliquet sed quis lacus. Nunc at ultrices quam. Vestibulum sit amet metus nec enim dapibus ullamcorper non eu quam. Duis mattis interdum scelerisque. Pellentesque eu lobortis nulla, quis laoreet lacus.",
  ];

  const MC_TEMPLATE = [
    [
      "core/heading",
      { level: 2, placeholder: __("Heading...", "multi-columns") },
    ],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[0] }],
    [
      "core/heading",
      { level: 3, placeholder: __("Sub-heading...", "multi-columns") },
    ],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[1] }],
  ];

  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  const onChangeColumnRuleWidth = (val) => {
    setAttributes({ columnRuleWidth: Number(val) });
  };
  const onChangeColumnRuleColor = (val) => {
    setAttributes({ columnRuleColor: val });
  };
  const onChangeDropCapColor = (val) => {
    setAttributes({ dropCapColor: val });
  };

  return (
    <>
      <InspectorControls>
        <PanelBody title={__("Column Settings", "multi-columns")}>
          <RangeControl
            label={__("Columns", "multi-columns")}
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label={__("Width", "multi-columns")}
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label={__("Gap", "multi-columns")}
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody
          title={__("Column Separator", "multi-columns")}
          initialOpen={false}
        >
          <SelectControl
            label={__("Style", "multi-columns")}
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: __("None", "multi-columns"),
                value: "none",
              },
              {
                label: __("Solid", "multi-columns"),
                value: "solid",
              },
              {
                label: __("Dotted", "multi-columns"),
                value: "dotted",
              },
              {
                label: __("Dashed", "multi-columns"),
                value: "dashed",
              },
              {
                label: __("Double", "multi-columns"),
                value: "double",
              },
              {
                label: __("Groove", "multi-columns"),
                value: "groove",
              },
              {
                label: __("Ridge", "multi-columns"),
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label={__("Width", "multi-columns")}
            onChange={onChangeColumnRuleWidth}
            value={columnRuleWidth}
            min={1}
            max={8}
          />
        </PanelBody>
        <PanelColorSettings
          title={__("Color settings", "multi-columns")}
          colorSettings={[
            {
              label: __("Separator color", "multi-columns"),
              value: columnRuleColor,
              onChange: onChangeColumnRuleColor,
            },
            {
              label: __("Drop-Cap color", "multi-columns"),
              value: dropCapColor,
              onChange: onChangeDropCapColor,
            },
          ]}
        ></PanelColorSettings>
      </InspectorControls>
      <div {...useBlockProps({ style: columnStyles })}>
        <InnerBlocks allowedBlocks={ALLOWED_BLOCKS} template={MC_TEMPLATE} />
      </div>
    </>
  );
}

スタイルがサイトのフロントエンドに反映されるように、save.js を変更します。

edit.js 同様、dropCapColor 属性の値を分割代入で取得し、その値をスタイルを定義するオブジェクト columnStyles で、CSS カスタムプロパティ --drop-cap-color に割り当てます。

import { useBlockProps, InnerBlocks } from "@wordpress/block-editor";

export default function save({ attributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    dropCapColor,
  } = attributes;

  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    '--drop-cap-color': dropCapColor,
  };

  return (
    <div {...useBlockProps.save({ style: columnStyles })}>
      <InnerBlocks.Content />
    </div>
  );
}

上記を保存して、エディターを再読込すると save.js を変更したため、以下のエラーが表示されますが、「復旧を試みる」をクリックしてブロックを回復させて保存すると、変更がフロントエンドに反映されます。

サイズの設定オプションを追加

ドロップキャップ(Drop-Cap)のサイズを変更できるようにします。

以下がドロップキャップの CSS です。サイズを変更するには font-size と line-height の値を変更します。

p:first-of-type:first-letter {
  color: var(--drop-cap-color);
  float: left;
  font-size: 3.8rem;
  line-height: 3.5rem;
  padding-right: 0.33rem;
}

また、ユーザーがサイズを簡単に変更できるように SelectControl コンポーネントを使って select 要素でサイズ(size)を選択できるようにします。

このため、必要なデータは、fontSize と lineHeight という2つの CSS プロパティと、select 要素から返されるサイズの値(size)になり、これらを格納するには属性をオブジェクト型にする必要があります。

block.json に dropCapSize 属性を追加します。

type に object を設定し、default に size と fontSize、lineHeight のデフォルト値を設定します(値は必要に応じて調整します)。

"dropCapSize": {
  "type": "object",
  "default": {
    "size": "small",
    "fontSize": "3.8rem",
    "lineHeight": "3.5rem"
  }
}
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Custom Multi Columns block for newspaper style.",
  "keywords": ["newspaper", "columns", "text"],
  "example": {
    "innerBlocks": [
      {
        "name": "core/heading",
        "attributes": {
          "level": 3,
          "content": "Heading"
        }
      },
      {
        "name": "core/paragraph",
        "attributes": {
          "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin finibus, lectus non interdum cursus, arcu sapien mollis lacus, et tincidunt odio nisi ut purus. Duis eleifend, magna placerat faucibus tincidunt."
        }
      },
      {
        "name": "core/heading",
        "attributes": {
          "level": 4,
          "content": "Sub-heading"
        }
      },
      {
        "name": "core/paragraph",
        "attributes": {
          "content": "Interdum et malesuada fames ac ante ipsum primis in faucibus. Cras vestibulum mauris diam. Praesent semper diam a efficitur iaculis. Nullam lacinia augue quis lorem accumsan tempus."
        }
      }
    ],
    "attributes": {
      "columnCount": 3,
      "columnWidth": 80
    },
    "viewportWidth": 720
  },
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    },
    "align": ["wide", "full"]
  },
  "attributes": {
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "columnGap": {
      "type": "integer",
      "default": 40
    },
    "columnRuleStyle": {
      "type": "string",
      "default": "solid"
    },
    "columnRuleWidth": {
      "type": "integer",
      "default": 1
    },
    "columnRuleColor": {
      "type": "string",
      "default": "#b8b8b8"
    },
    "dropCapColor": {
      "type": "string",
      "default": "#b8b8b8"
    },
    "dropCapSize": {
      "type": "object",
      "default": {
        "size": "small",
        "fontSize": "3.8rem",
        "lineHeight": "3.5rem"
      }
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "parent": ["core/post-content"],
  "styles": [
    {
      "name": "default",
      "label": "Default",
      "isDefault": true
    },
    {
      "name": "drop-cap",
      "label": "Drop-Cap"
    }
  ],
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "viewScript": "file:./view.js"
}

edit.js の Edit() 関数で、上記で設定した dropCapSize 属性を attributes から分割代入で取得します。

const {
  columnCount,
  columnWidth,
  columnGap,
  columnRuleStyle,
  columnRuleWidth,
  columnRuleColor,
  dropCapColor,
  dropCapSize,
} = attributes;

続いてスタイルを定義する columnStyles に Drop-Cap のサイズを設定するプロパティを追加します。

この場合、dropCapSize 属性(オブジェクト)から fontSize プロパティ(dropCapSize.fontSize)と lineHeight プロパティ(dropCapSize.lineHeight)の値を、2つの新しい CSS カスタムプロパティ(--drop-cap-font-size と --drop-cap-line-height)にそれぞれ割り当てます。

const columnStyles = {
  columnCount,
  columnWidth,
  columnGap,
  columnRuleStyle,
  columnRuleWidth,
  columnRuleColor,
  '--drop-cap-color': dropCapColor,
  '--drop-cap-font-size': dropCapSize.fontSize,
  '--drop-cap-line-height': dropCapSize.lineHeight,
};

JSX に SelectControl コンポーネントを追加します。SelectControl コンポーネントはすでにインポートされているので、再度インポートする必要はありません。

このオプションは、インスペクターパネルにすでにある他のパネルのいずれにも関連しないため、SelectControl コンポーネントを新しい PanelBody コンポーネントに配置します。

PanelBody コンポーネントのタイトル(title)を「Drop-Cap」に、initialOpen に false を指定して初期状態では閉じるようにしています。

また、__() を使ってインターフェイスに表示されるすべての文字列(title や label)を翻訳可能にします。

<PanelBody title={__("Drop-Cap", "multi-columns")} initialOpen={false}>
  <SelectControl
    label={__("Size", "multi-columns")}
    onChange={onChangeDropCapSize}
    value={dropCapSize.size}
    options={[
      {
        label: __("Small", "multi-columns"),
        value: "small",
      },
      {
        label: __("Large", "multi-columns"),
        value: "large",
      },
    ]}
  />
</PanelBody>

SelectControl は、dropCapSize 属性の size プロパティ(dropCapSize.size)から値を取得し、Small と Large というラベルの付いた2つのオプションを提供し、それぞれ small と large という値を返します。

onChange イベントのハンドラー関数では、パラメーターとして small または large のいずれかを含む文字列を受け取るので、受け取った値に応じて dropCapSize 属性のオブジェクトのプロパティを更新するようにします。

以下のハンドラー関数 onChangeDropCapSize の定義を edit.js に追加します。

switch ステートメントを使って、SelectControl で small または large のどちらが選択されているかに応じて、size、fontSize、および lineHeight を含む新しいオブジェクトで dropCapSize 属性を更新します。

const onChangeDropCapSize = (val) => {
  switch (val) {
    case "small":
      setAttributes({
        dropCapSize: {
          size: "small",
          fontSize: "3.8rem",
          lineHeight: "3.5rem",
        },
      });
      break;
    case "large":
      setAttributes({
        dropCapSize: {
          size: "large",
          fontSize: "6.2rem",
          lineHeight: "5.2rem",
        },
      });
      break;
    default:
      setAttributes({
        dropCapSize: {
          size: "small",
          fontSize: "3.8rem",
          lineHeight: "3.5rem",
        },
      });
  }
};
import { __ } from "@wordpress/i18n";
import {
  useBlockProps,
  InnerBlocks,
  InspectorControls,
  PanelColorSettings,
} from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    dropCapColor,
    dropCapSize,
  } = attributes;

  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    "--drop-cap-color": dropCapColor,
    "--drop-cap-font-size": dropCapSize.fontSize,
    "--drop-cap-line-height": dropCapSize.lineHeight,
  };

  const ALLOWED_BLOCKS = ["core/heading", "core/paragraph", "core/image"];

  const TEMPLATE_PARAGRAPHS = [
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt nec tortor in laoreet. Fusce blandit augue vel felis pulvinar, a gravida nibh maximus. Morbi imperdiet mattis sapien, in dignissim enim vehicula at. Fusce egestas sed dui et feugiat. In hac habitasse platea dictumst. Proin sed convallis turpis, a dictum metus. ",
    "Nullam a nisi blandit, aliquet felis condimentum, efficitur dui. Maecenas sed efficitur magna. Praesent vel quam tristique velit facilisis laoreet. Integer ac leo eu enim mattis aliquet sed quis lacus. Nunc at ultrices quam. Vestibulum sit amet metus nec enim dapibus ullamcorper non eu quam. Duis mattis interdum scelerisque. Pellentesque eu lobortis nulla, quis laoreet lacus.",
  ];

  const MC_TEMPLATE = [
    [
      "core/heading",
      { level: 2, placeholder: __("Heading...", "multi-columns") },
    ],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[0] }],
    [
      "core/heading",
      { level: 3, placeholder: __("Sub-heading...", "multi-columns") },
    ],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[1] }],
  ];

  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  const onChangeColumnRuleWidth = (val) => {
    setAttributes({ columnRuleWidth: Number(val) });
  };
  const onChangeColumnRuleColor = (val) => {
    setAttributes({ columnRuleColor: val });
  };
  const onChangeDropCapColor = (val) => {
    setAttributes({ dropCapColor: val });
  };
  const onChangeDropCapSize = (val) => {
    switch (val) {
      case "small":
        setAttributes({
          dropCapSize: {
            size: "small",
            fontSize: "3.8rem",
            lineHeight: "3.5rem",
          },
        });
        break;
      case "large":
        setAttributes({
          dropCapSize: {
            size: "large",
            fontSize: "6.2rem",
            lineHeight: "5.2rem",
          },
        });
        break;
      default:
        setAttributes({
          dropCapSize: {
            size: "small",
            fontSize: "3.8rem",
            lineHeight: "3.5rem",
          },
        });
    }
  };

  return (
    <>
      <InspectorControls>
        <PanelBody title={__("Column Settings", "multi-columns")}>
          <RangeControl
            label={__("Columns", "multi-columns")}
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label={__("Width", "multi-columns")}
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label={__("Gap", "multi-columns")}
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody
          title={__("Column Separator", "multi-columns")}
          initialOpen={false}
        >
          <SelectControl
            label={__("Style", "multi-columns")}
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: __("None", "multi-columns"),
                value: "none",
              },
              {
                label: __("Solid", "multi-columns"),
                value: "solid",
              },
              {
                label: __("Dotted", "multi-columns"),
                value: "dotted",
              },
              {
                label: __("Dashed", "multi-columns"),
                value: "dashed",
              },
              {
                label: __("Double", "multi-columns"),
                value: "double",
              },
              {
                label: __("Groove", "multi-columns"),
                value: "groove",
              },
              {
                label: __("Ridge", "multi-columns"),
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label={__("Width", "multi-columns")}
            onChange={onChangeColumnRuleWidth}
            value={columnRuleWidth}
            min={1}
            max={8}
          />
        </PanelBody>
        <PanelBody title={__("Drop-Cap", "multi-columns")} initialOpen={false}>
          <SelectControl
            label={__("Size", "multi-columns")}
            onChange={onChangeDropCapSize}
            value={dropCapSize.size}
            options={[
              {
                label: __("Small", "multi-columns"),
                value: "small",
              },
              {
                label: __("Large", "multi-columns"),
                value: "large",
              },
            ]}
          />
        </PanelBody>
        <PanelColorSettings
          title={__("Color settings", "multi-columns")}
          colorSettings={[
            {
              label: __("Separator color", "multi-columns"),
              value: columnRuleColor,
              onChange: onChangeColumnRuleColor,
            },
            {
              label: __("Drop-Cap color", "multi-columns"),
              value: dropCapColor,
              onChange: onChangeDropCapColor,
            },
          ]}
        ></PanelColorSettings>
      </InspectorControls>
      <div {...useBlockProps({ style: columnStyles })}>
        <InnerBlocks allowedBlocks={ALLOWED_BLOCKS} template={MC_TEMPLATE} />
      </div>
    </>
  );
}

この時点で、インスペクターに追加した「Drop-Cap」パネルの「Size」のセレクトボックスを変更すると、ラッパー要素に展開される style 属性の CSS カスタムプロパティの値は更新されますが、ドロップキャップのサイズ(スタイル)は変わりません。

ドロップキャップのサイズ(スタイル)が反映されるようにするため、style.scss でドロップキャップのセレクタ p:first-of-type:first-letter の font-size と line-height に CSS カスタムプロパティの値を var() 関数を使って設定します。

.wp-block-create-block-multi-columns {
  box-sizing: border-box;
  column-fill: balance;

  &.is-style-drop-cap {
    p:first-of-type:first-letter {
      color: var(--drop-cap-color);
      float: left;
      font-size: var(--drop-cap-font-size);
      line-height: var(--drop-cap-line-height);
      padding-right: 0.33rem;
    }
  }
}

これでエディターでドロップキャップのサイズを切り替えることができるようになります。

スタイルがサイトのフロントエンドに反映されるように、save.js を変更します。

edit.js 同様、dropCapSize 属性の値を分割代入で取得し、そのプロパティの値 dropCapSize.fontSize と dropCapSize.lineHeight をスタイルを定義するオブジェクト columnStyles で、CSS カスタムプロパティ --drop-cap-font-size と --drop-cap-line-height にそれぞれ割り当てます。

import { useBlockProps, InnerBlocks } from "@wordpress/block-editor";

export default function save({ attributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    dropCapColor,
    dropCapSize,
  } = attributes;

  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    "--drop-cap-color": dropCapColor,
    "--drop-cap-font-size": dropCapSize.fontSize,
    "--drop-cap-line-height": dropCapSize.lineHeight,
  };

  return (
    <div {...useBlockProps.save({ style: columnStyles })}>
      <InnerBlocks.Content />
    </div>
  );
}

上記を保存してエディターを再読込すると save.js を変更したため、エラーが表示されますが、「復旧を試みる」をクリックしてブロックを回復させて保存すると、変更がフロントエンドに反映されます。

条件付きでコントロールを表示

コントロールが不要な場合は、表示されないようにインターフェイスを整理します。

この例の場合、「Default」スタイルが選択されているときにドロップキャップに関連するコントロールは不要なので非表示にし、「Drop-Cap」スタイルが選択されているときにのみ表示するようにします。

ブロックスタイルのバリエーションが選択されると、WordPress はブロックにクラスを追加し、そのクラスは is-style-{name} という形式になります。

{name} は、block.json で定義されているブロックスタイル(styles プロパティ)の name プロパティに割り当てられた値です(ブロックスタイルの追加)。

クラス名は attributes の className プロパティ(attributes.className)で参照できます。

「Drop-Cap」スタイルのバリエーションが選択されると、クラス is-style-drop-cap がブロックに追加されるので、そのクラスの存在を判定すれば、条件付きでコントロールを表示することができます。

ドロップキャップのサイズのコントロール

ドロップキャップのサイズのコントロールは PanelBody コンポーネントの中に配置されています。

以下のように三項演算子を使用してクラスの存在を判定し、PanelBody コンポーネントまたは null のいずれかをレンダリングする方法があります。

※ JSX 内のすべての JavaScript コードは中括弧 {} で囲む必要があります。

{attributes.className === "is-style-drop-cap" ? (
  <PanelBody title={__("Drop-Cap", "multi-columns")} initialOpen={false} >
    <SelectControl
      label={__("Size", "multi-columns")}
      onChange={onChangeDropCapSize}
      value={dropCapSize.size}
      options={[
        {
          label: __("Small", "multi-columns"),
          value: "small",
        },
        {
          label: __("Large", "multi-columns"),
          value: "large",
        },
      ]}
    />
  </PanelBody>
) : null}

上記の方法でも機能しますが、以下のように論理 AND 演算子 (&&) を使用すれば、評価される式が false を返す場合に null をレンダリングする必要がないため、こちらの方法がよく使われます(こちらは、React で条件付きレンダリングを行う一般的な方法です)。

React Docs: 条件付きレンダー > 論理 AND 演算子 (&&)

{attributes.className === "is-style-drop-cap" && (
  <PanelBody title={__("Drop-Cap", "multi-columns")} initialOpen={false} >
    <SelectControl
      label={__("Size", "multi-columns")}
      onChange={onChangeDropCapSize}
      value={dropCapSize.size}
      options={[
        {
          label: __("Small", "multi-columns"),
          value: "small",
        },
        {
          label: __("Large", "multi-columns"),
          value: "large",
        },
      ]}
    />
  </PanelBody>
)}

これが機能するのは、論理 AND 演算子 (&&) が、両方のオペランドが true の場合にのみ true と評価されるためです。

コンポーネントのレンダリング部分は常に true と評価されるため、この例では、条件全体の値は最初のオペランド(クラスの存在判定)の真偽に依存します。最初のオペランドが false の場合、条件全体が false と評価され、コンポーネントはレンダリングされません。

ドロップキャップの色のコントロール

ドロップキャップの色を変更するオプションは、以下のように PanelColorSettings コンポーネントの colorSettings プロパティに配列の要素として設定されています。

この場合、条件付きで配列に要素を含めることはできません。

<PanelColorSettings
  title={__("Color settings", "multi-columns")}
  colorSettings={[
    {
      label: __("Separator color", "multi-columns"),
      value: columnRuleColor,
      onChange: onChangeColumnRuleColor,
    },
    // ドロップキャップの色のオプション
    {
      label: __("Drop-Cap color", "multi-columns"),
      value: dropCapColor,
      onChange: onChangeDropCapColor,
    },
  ]}
></PanelColorSettings>

そのため、以下のような条件付きで異なる配列を返す変数を定義します。

変数には、colorSettingsDropDown などのわかりやすい名前を付け、三項演算子を使用して、ブロックに is-style-drop-cap クラスがあるかどうかに応じて2つの配列のいずれかを返します。

const colorSettingsDropDown =
  attributes.className === "is-style-drop-cap"
    ? [
        {
          value: columnRuleColor,
          onChange: onChangeColumnRuleColor,
          label: __("Separator color", "multi-columns"),
        },
        {
          value: dropCapColor,
          onChange: onChangeDropCapColor,
          label: __("Drop-Cap color", "multi-columns"),
        },
      ]
    : [
        {
          value: columnRuleColor,
          onChange: onChangeColumnRuleColor,
          label: __("Separator color", "multi-columns"),
        },
      ];

ブロックに is-style-drop-cap クラスがある場合は、columnRuleColor コントロールと dropCapColor コントロールの両方が含まれる1つ目の配列を返します。

ブロックに is-style-drop-cap クラスがない場合は、columnRuleColor コントロールのみが含まれる2つ目の配列を返します。

※ 上記のコードには onChangeColumnRuleColor および onChangeDropCapColor ハンドラー関数への参照が含まれているため、これらのハンドラー関数定義の後に配置する必要があります。

そして、PanelColorSettings コンポーネントの colorSettings プロパティに定義した変数を指定します。

<PanelColorSettings
  title={__("Color settings", "multi-columns")}
  initialOpen={false}
  colorSettings={colorSettingsDropDown}
></PanelColorSettings>

これで Default のブロックスタイルを選択した場合は、ドロップキャップのコントロールは表示されず、

Drop-cap のブロックスタイルを選択した場合にのみ、ドロップキャップのコントロールは表示されます。

import { __ } from "@wordpress/i18n";
import {
  useBlockProps,
  InnerBlocks,
  InspectorControls,
  PanelColorSettings,
} from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";
import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    dropCapColor,
    dropCapSize,
  } = attributes;

  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    "--drop-cap-color": dropCapColor,
    "--drop-cap-font-size": dropCapSize.fontSize,
    "--drop-cap-line-height": dropCapSize.lineHeight,
  };

  const ALLOWED_BLOCKS = ["core/heading", "core/paragraph", "core/image"];

  const TEMPLATE_PARAGRAPHS = [
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt nec tortor in laoreet. Fusce blandit augue vel felis pulvinar, a gravida nibh maximus. Morbi imperdiet mattis sapien, in dignissim enim vehicula at. Fusce egestas sed dui et feugiat. In hac habitasse platea dictumst. Proin sed convallis turpis, a dictum metus. ",
    "Nullam a nisi blandit, aliquet felis condimentum, efficitur dui. Maecenas sed efficitur magna. Praesent vel quam tristique velit facilisis laoreet. Integer ac leo eu enim mattis aliquet sed quis lacus. Nunc at ultrices quam. Vestibulum sit amet metus nec enim dapibus ullamcorper non eu quam. Duis mattis interdum scelerisque. Pellentesque eu lobortis nulla, quis laoreet lacus.",
  ];

  const MC_TEMPLATE = [
    [
      "core/heading",
      { level: 2, placeholder: __("Heading...", "multi-columns") },
    ],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[0] }],
    [
      "core/heading",
      { level: 3, placeholder: __("Sub-heading...", "multi-columns") },
    ],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[1] }],
  ];

  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  const onChangeColumnRuleWidth = (val) => {
    setAttributes({ columnRuleWidth: Number(val) });
  };
  const onChangeColumnRuleColor = (val) => {
    setAttributes({ columnRuleColor: val });
  };
  const onChangeDropCapColor = (val) => {
    setAttributes({ dropCapColor: val });
  };
  const onChangeDropCapSize = (val) => {
    switch (val) {
      case "small":
        setAttributes({
          dropCapSize: {
            size: "small",
            fontSize: "3.8rem",
            lineHeight: "3.5rem",
          },
        });
        break;
      case "large":
        setAttributes({
          dropCapSize: {
            size: "large",
            fontSize: "6.2rem",
            lineHeight: "5.2rem",
          },
        });
        break;
      default:
        setAttributes({
          dropCapSize: {
            size: "small",
            fontSize: "3.8rem",
            lineHeight: "3.5rem",
          },
        });
    }
  };

  const colorSettingsDropDown =
    attributes.className === "is-style-drop-cap"
      ? [
          {
            value: columnRuleColor,
            onChange: onChangeColumnRuleColor,
            label: __("Separator color", "multi-columns"),
          },
          {
            value: dropCapColor,
            onChange: onChangeDropCapColor,
            label: __("Drop-Cap color", "multi-columns"),
          },
        ]
      : [
          {
            value: columnRuleColor,
            onChange: onChangeColumnRuleColor,
            label: __("Separator color", "multi-columns"),
          },
        ];

  return (
    <>
      <InspectorControls>
        <PanelBody title={__("Column Settings", "multi-columns")}>
          <RangeControl
            label={__("Columns", "multi-columns")}
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label={__("Width", "multi-columns")}
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label={__("Gap", "multi-columns")}
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody
          title={__("Column Separator", "multi-columns")}
          initialOpen={false}
        >
          <SelectControl
            label={__("Style", "multi-columns")}
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: __("None", "multi-columns"),
                value: "none",
              },
              {
                label: __("Solid", "multi-columns"),
                value: "solid",
              },
              {
                label: __("Dotted", "multi-columns"),
                value: "dotted",
              },
              {
                label: __("Dashed", "multi-columns"),
                value: "dashed",
              },
              {
                label: __("Double", "multi-columns"),
                value: "double",
              },
              {
                label: __("Groove", "multi-columns"),
                value: "groove",
              },
              {
                label: __("Ridge", "multi-columns"),
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label={__("Width", "multi-columns")}
            onChange={onChangeColumnRuleWidth}
            value={columnRuleWidth}
            min={1}
            max={8}
          />
        </PanelBody>
        {attributes.className === "is-style-drop-cap" && (
          <PanelBody
            title={__("Drop-Cap", "multi-columns")}
            initialOpen={false}
          >
            <SelectControl
              label={__("Size", "multi-columns")}
              onChange={onChangeDropCapSize}
              value={dropCapSize.size}
              options={[
                {
                  label: __("Small", "multi-columns"),
                  value: "small",
                },
                {
                  label: __("Large", "multi-columns"),
                  value: "large",
                },
              ]}
            />
          </PanelBody>
        )}
        <PanelColorSettings
          title={__("Color settings", "multi-columns")}
          initialOpen={false}
          colorSettings={colorSettingsDropDown}
        ></PanelColorSettings>
      </InspectorControls>
      <div {...useBlockProps({ style: columnStyles })}>
        <InnerBlocks allowedBlocks={ALLOWED_BLOCKS} template={MC_TEMPLATE} />
      </div>
    </>
  );
}

これでブロックの作成は完了です。

クリーンアップ

不要なコードやファイルを削除します。

ファイルを削除する前に、削除するファイルの読み込み部分をコード上から削除しておきます。

※ 先にファイルを削除するとターミナル上にエラーが出てしまいます(後からコードを修正すればエラーは消えます)。

このカスタムブロックでは src ディレクトリの view.js ファイルと editor.scss ファイルは使用しないので、まず block.json と edit.js で不要なコードを削除して、その後これらの不要なファイルを削除します。

src/view.js

view.js ファイルはブロックが表示されるときにフロントエンド内に読み込まれるファイルです。

create-block コマンドでひな型を作成した際に生成されますが、このカスタムブロックでは使用しないので削除します。

以下はデフォルトで生成された view.js ファイルです。console.log() のみが記述されています。

 /**
 * Use this file for JavaScript code that you want to run in the front-end
 * on posts/pages that contain this block.
 *
 * When this file is defined as the value of the `viewScript` property
 * in `block.json` it will be enqueued on the front end of the site.
 *
 * Example:
 *
 * ```js
 * {
 *   "viewScript": "file:./view.js"
 * }
 * ```
 *
 * If you're not making any changes to this file because your project doesn't need any
 * JavaScript running in the front-end, then you should delete this file and remove
 * the `viewScript` property from `block.json`.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#view-script
 */

/* eslint-disable no-console */
console.log( 'Hello World! (from create-block-multi-columns block)' );
/* eslint-enable no-console */

src/editor.scss

editor.scss ファイルはエディター内でブロックに適用されるスタイルを記述するファイルです。

create-block コマンドにより、以下の editor.scss ファイルが生成されています。

エディターでブロックを確認すると、オレンジ色の点線の枠線が表示されているのは、以下に記述されているスタイルによるものです。このカスタムブロックでは使用しないので削除します。

/**
 * The following styles get applied inside the editor only.
 *
 * Replace them with your own styles or remove the file completely.
 */

.wp-block-create-block-multi-columns {
  border: 1px dotted #f00;
}

block.json で関連プロパティを削除

block.json で、以下の editorStyle プロパティと viewScript プロパティの行を削除します。

"editorStyle": "file:./index.css",
"viewScript": "file:./view.js"

edit.js で editor.scss のインポートを削除

edit.js で以下の editor.scss のインポートの行を削除します。

import "./editor.scss";

view.js と editor.scss を削除

src ディレクトリの view.js ファイルと editor.scss ファイルを削除します。

ビルド

開発が完了したら、変更がすべて保存されていることを確認して、ターミナルで control + c を押して npm run start コマンド(開発モード)を終了します。

ターミナルで npm run build を実行して本番環境用にビルドを実行します。

% npm run build

「webpack 5.97.0 compiled successfully in 797 ms」のようなビルドが成功したメッセージが表示されます。

plugin-zip

ターミナルで npm run plugin-zip コマンドを実行して、作成したカスタムブロックの zip ファイルを作成することができます。

% npm run plugin-zip

npm run plugin-zip コマンドを実行すると、例えば以下のように、zip ファイルに含まれるファイルがレスポンスに表示され、zip ファイルがプロジェクトのルートディレクトリに生成されます。

デフォルトでは、プラグインファイルと build ディレクトリのファイル、及び readme.txt、もしあれば言語ディレクトリ(languages)が含まれます。

% npm run plugin-zip

> multi-columns@0.1.0 plugin-zip
> wp-scripts plugin-zip

Creating archive for `multi-columns` plugin... 🎁

Using Plugin Handbook best practices to discover files:

  Adding `multi-columns.php`.
  Adding `readme.txt`.
  Adding `build/block.json`.
  Adding `build/index.asset.php`.
  Adding `build/index.js`.
  Adding `build/style-index-rtl.css`.
  Adding `build/style-index.css`.

Done. `multi-columns.zip` is ready! 🎉

zip ファイルを使ってプラグインをインストール

作成された zip ファイルを使って、別のサイトでプラグインとしてインストールすることができます。

ダッシュボードのプラグイン画面で、「新規プラグインを追加」をクリックします。

「プラグインをアップロード」をクリックします。

ファイルを選択して「今すぐインストール」をクリックします。

「プラグインを有効化」をクリックして完了です。

zip ファイルに src フォルダも含める

デフォルトでは npm run plugin-zip を実行すると src フォルダのファイルは含まれません。

src フォルダのファイルも含めたい場合は、package.json に files フィールドの行を追加し、zip ファイルに含めるファイルやディレクトリを指定します。

files フィールドを指定した場合、含める必要のあるファイルやディレクトリを全て指定する必要があります(readme.txt と package.json は指定しなくても自動的に含まれます)。

以下では files フィールドに build と src ディレクトリ及び multi-columns.php(プラグインファイル)を指定しています。

{
  "name": "multi-columns",
  "version": "0.1.0",
  "description": "Example block scaffolded with Create Block tool.",
  "author": "The WordPress Contributors",
  "license": "GPL-2.0-or-later",
  "main": "build/index.js",
  "scripts": {
    "build": "wp-scripts build",
    "format": "wp-scripts format",
    "lint:css": "wp-scripts lint-style",
    "lint:js": "wp-scripts lint-js",
    "packages-update": "wp-scripts packages-update",
    "plugin-zip": "wp-scripts plugin-zip",
    "start": "wp-scripts start"
  },
  "files": [ "build", "src", "multi-columns.php" ],
  "devDependencies": {
    "@wordpress/scripts": "^30.6.0"
  }
}

上記のように package.json を変更して、npm run plugin-zip を実行すると、例えば以下のように src ディレクトリのファイルも zip ファイルに含まれます。

% npm run plugin-zip

> multi-columns@0.1.0 plugin-zip
> wp-scripts plugin-zip

Creating archive for `multi-columns` plugin... 🎁

Using the `files` field from `package.json` to detect files:

  Adding `build/style-index-rtl.css`.
  Adding `build/style-index.css`.
  Adding `src/edit.js`.
  Adding `build/index.js`.
  Adding `src/index.js`.
  Adding `src/components/number-control.js`.
  Adding `src/save.js`.
  Adding `build/block.json`.
  Adding `src/block.json`.
  Adding `package.json`.
  Adding `build/index.asset.php`.
  Adding `multi-columns.php`.
  Adding `src/style.scss`.
  Adding `readme.txt`.

Done. `multi-columns.zip` is ready! 🎉

ファイルサンプル

最終的な各ファイルのコードです。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",
  "version": "0.1.0",
  "title": "Multi Columns",
  "category": "design",
  "icon": "columns",
  "description": "Custom Multi Columns block for newspaper style.",
  "keywords": ["newspaper", "columns", "text"],
  "example": {
    "innerBlocks": [
      {
        "name": "core/heading",
        "attributes": {
          "level": 3,
          "content": "Heading"
        }
      },
      {
        "name": "core/paragraph",
        "attributes": {
          "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin finibus, lectus non interdum cursus, arcu sapien mollis lacus, et tincidunt odio nisi ut purus. Duis eleifend, magna placerat faucibus tincidunt."
        }
      },
      {
        "name": "core/heading",
        "attributes": {
          "level": 4,
          "content": "Sub-heading"
        }
      },
      {
        "name": "core/paragraph",
        "attributes": {
          "content": "Interdum et malesuada fames ac ante ipsum primis in faucibus. Cras vestibulum mauris diam. Praesent semper diam a efficitur iaculis. Nullam lacinia augue quis lorem accumsan tempus."
        }
      }
    ],
    "attributes": {
      "columnCount": 3,
      "columnWidth": 80
    },
    "viewportWidth": 720
  },
  "supports": {
    "html": true,
    "color": {},
    "spacing": {
      "margin": true,
      "padding": true,
      "blockGap": true
    },
    "align": ["wide", "full"]
  },
  "attributes": {
    "columnCount": {
      "type": "integer",
      "default": 4
    },
    "columnWidth": {
      "type": "integer",
      "default": 200
    },
    "columnGap": {
      "type": "integer",
      "default": 40
    },
    "columnRuleStyle": {
      "type": "string",
      "default": "solid"
    },
    "columnRuleWidth": {
      "type": "integer",
      "default": 1
    },
    "columnRuleColor": {
      "type": "string",
      "default": "#b8b8b8"
    },
    "dropCapColor": {
      "type": "string",
      "default": "#b8b8b8"
    },
    "dropCapSize": {
      "type": "object",
      "default": {
        "size": "small",
        "fontSize": "3.8rem",
        "lineHeight": "3.5rem"
      }
    },
    "style": {
      "type": "object",
      "default": {
        "color": {
          "text": "#3a3a3a",
          "background": "#f3faf1"
        },
        "spacing": {
          "padding": {
            "top": "20px",
            "right": "20px",
            "bottom": "20px",
            "left": "20px"
          },
          "margin": {
            "top": "20px",
            "right": "0px",
            "bottom": "20px",
            "left": "0px"
          }
        }
      }
    }
  },
  "parent": ["core/post-content"],
  "styles": [
    {
      "name": "default",
      "label": "Default",
      "isDefault": true
    },
    {
      "name": "drop-cap",
      "label": "Drop-Cap"
    }
  ],
  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "style": "file:./style-index.css"
}
import { __ } from "@wordpress/i18n";
import {
  useBlockProps,
  InnerBlocks,
  InspectorControls,
  PanelColorSettings,
} from "@wordpress/block-editor";
import { PanelBody, RangeControl, SelectControl } from "@wordpress/components";
import NumberControl from "./components/number-control";

export default function Edit({ attributes, setAttributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    dropCapColor,
    dropCapSize,
  } = attributes;

  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    "--drop-cap-color": dropCapColor,
    "--drop-cap-font-size": dropCapSize.fontSize,
    "--drop-cap-line-height": dropCapSize.lineHeight,
  };

  const ALLOWED_BLOCKS = ["core/heading", "core/paragraph", "core/image"];

  const TEMPLATE_PARAGRAPHS = [
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tincidunt nec tortor in laoreet. Fusce blandit augue vel felis pulvinar, a gravida nibh maximus. Morbi imperdiet mattis sapien, in dignissim enim vehicula at. Fusce egestas sed dui et feugiat. In hac habitasse platea dictumst. Proin sed convallis turpis, a dictum metus. ",
    "Nullam a nisi blandit, aliquet felis condimentum, efficitur dui. Maecenas sed efficitur magna. Praesent vel quam tristique velit facilisis laoreet. Integer ac leo eu enim mattis aliquet sed quis lacus. Nunc at ultrices quam. Vestibulum sit amet metus nec enim dapibus ullamcorper non eu quam. Duis mattis interdum scelerisque. Pellentesque eu lobortis nulla, quis laoreet lacus.",
  ];

  const MC_TEMPLATE = [
    [
      "core/heading",
      { level: 2, placeholder: __("Heading...", "multi-columns") },
    ],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[0] }],
    [
      "core/heading",
      { level: 3, placeholder: __("Sub-heading...", "multi-columns") },
    ],
    ["core/paragraph", { placeholder: TEMPLATE_PARAGRAPHS[1] }],
  ];

  const onChangeColumnCount = (val) => {
    setAttributes({ columnCount: val });
  };
  const onChangeColumnWidth = (val) => {
    setAttributes({ columnWidth: Number(val) });
  };
  const onChangeColumnGap = (val) => {
    setAttributes({ columnGap: Number(val) });
  };
  const onChangeColumnRuleStyle = (val) => {
    setAttributes({ columnRuleStyle: val });
  };
  const onChangeColumnRuleWidth = (val) => {
    setAttributes({ columnRuleWidth: Number(val) });
  };
  const onChangeColumnRuleColor = (val) => {
    setAttributes({ columnRuleColor: val });
  };
  const onChangeDropCapColor = (val) => {
    setAttributes({ dropCapColor: val });
  };
  const onChangeDropCapSize = (val) => {
    switch (val) {
      case "small":
        setAttributes({
          dropCapSize: {
            size: "small",
            fontSize: "3.8rem",
            lineHeight: "3.5rem",
          },
        });
        break;
      case "large":
        setAttributes({
          dropCapSize: {
            size: "large",
            fontSize: "6.2rem",
            lineHeight: "5.2rem",
          },
        });
        break;
      default:
        setAttributes({
          dropCapSize: {
            size: "small",
            fontSize: "3.8rem",
            lineHeight: "3.5rem",
          },
        });
    }
  };

  const colorSettingsDropDown =
    attributes.className === "is-style-drop-cap"
      ? [
          {
            value: columnRuleColor,
            onChange: onChangeColumnRuleColor,
            label: __("Separator color", "multi-columns"),
          },
          {
            value: dropCapColor,
            onChange: onChangeDropCapColor,
            label: __("Drop-Cap color", "multi-columns"),
          },
        ]
      : [
          {
            value: columnRuleColor,
            onChange: onChangeColumnRuleColor,
            label: __("Separator color", "multi-columns"),
          },
        ];

  return (
    <>
      <InspectorControls>
        <PanelBody title={__("Column Settings", "multi-columns")}>
          <RangeControl
            label={__("Columns", "multi-columns")}
            value={columnCount}
            onChange={onChangeColumnCount}
            min={2}
            max={6}
          />
          <NumberControl
            label={__("Width", "multi-columns")}
            value={columnWidth}
            onChange={onChangeColumnWidth}
            min={120}
            max={500}
            step={10}
          />
          <NumberControl
            label={__("Gap", "multi-columns")}
            onChange={onChangeColumnGap}
            value={columnGap}
            min={10}
            max={100}
          />
        </PanelBody>
        <PanelBody
          title={__("Column Separator", "multi-columns")}
          initialOpen={false}
        >
          <SelectControl
            label={__("Style", "multi-columns")}
            onChange={onChangeColumnRuleStyle}
            value={columnRuleStyle}
            options={[
              {
                label: __("None", "multi-columns"),
                value: "none",
              },
              {
                label: __("Solid", "multi-columns"),
                value: "solid",
              },
              {
                label: __("Dotted", "multi-columns"),
                value: "dotted",
              },
              {
                label: __("Dashed", "multi-columns"),
                value: "dashed",
              },
              {
                label: __("Double", "multi-columns"),
                value: "double",
              },
              {
                label: __("Groove", "multi-columns"),
                value: "groove",
              },
              {
                label: __("Ridge", "multi-columns"),
                value: "ridge",
              },
            ]}
          />
          <NumberControl
            label={__("Width", "multi-columns")}
            onChange={onChangeColumnRuleWidth}
            value={columnRuleWidth}
            min={1}
            max={8}
          />
        </PanelBody>
        {attributes.className === "is-style-drop-cap" && (
          <PanelBody
            title={__("Drop-Cap", "multi-columns")}
            initialOpen={false}
          >
            <SelectControl
              label={__("Size", "multi-columns")}
              onChange={onChangeDropCapSize}
              value={dropCapSize.size}
              options={[
                {
                  label: __("Small", "multi-columns"),
                  value: "small",
                },
                {
                  label: __("Large", "multi-columns"),
                  value: "large",
                },
              ]}
            />
          </PanelBody>
        )}
        <PanelColorSettings
          title={__("Color settings", "multi-columns")}
          initialOpen={false}
          colorSettings={colorSettingsDropDown}
        ></PanelColorSettings>
      </InspectorControls>
      <div {...useBlockProps({ style: columnStyles })}>
        <InnerBlocks allowedBlocks={ALLOWED_BLOCKS} template={MC_TEMPLATE} />
      </div>
    </>
  );
}
import { registerBlockType } from '@wordpress/blocks';
import './style.scss';
import Edit from './edit';
import save from './save';
import metadata from './block.json';

registerBlockType( metadata.name, {
  edit: Edit,
  save,
} );
import { useBlockProps, InnerBlocks } from "@wordpress/block-editor";

export default function save({ attributes }) {
  const {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    dropCapColor,
    dropCapSize,
  } = attributes;

  const columnStyles = {
    columnCount,
    columnWidth,
    columnGap,
    columnRuleStyle,
    columnRuleWidth,
    columnRuleColor,
    "--drop-cap-color": dropCapColor,
    "--drop-cap-font-size": dropCapSize.fontSize,
    "--drop-cap-line-height": dropCapSize.lineHeight,
  };

  return (
    <div {...useBlockProps.save({ style: columnStyles })}>
      <InnerBlocks.Content />
    </div>
  );
}
.wp-block-create-block-multi-columns {
  box-sizing: border-box;
  column-fill: balance;

  &.is-style-drop-cap {
    p:first-of-type:first-letter {
      color: var(--drop-cap-color);
      float: left;
      font-size: var(--drop-cap-font-size);
      line-height: var(--drop-cap-line-height);
      padding-right: 0.33rem;
    }
  }
}
<?php
/**
* Plugin Name:       Multi Columns
* Description:       Custom Multi Columns block for newspaper style.
* Requires at least: 6.6
* Requires PHP:      7.2
* Version:           0.1.0
* Author:            The WordPress Contributors
* License:           GPL-2.0-or-later
* License URI:       https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain:       multi-columns
*
* @package CreateBlock
*/

if ( ! defined( 'ABSPATH' ) ) {
  exit; // Exit if accessed directly.
}

function create_block_multi_columns_block_init() {
  register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'create_block_multi_columns_block_init' );
{
  "name": "multi-columns",
  "version": "0.1.0",
  "description": "Example block scaffolded with Create Block tool.",
  "author": "The WordPress Contributors",
  "license": "GPL-2.0-or-later",
  "main": "build/index.js",
  "scripts": {
    "build": "wp-scripts build",
    "format": "wp-scripts format",
    "lint:css": "wp-scripts lint-style",
    "lint:js": "wp-scripts lint-js",
    "packages-update": "wp-scripts packages-update",
    "plugin-zip": "wp-scripts plugin-zip",
    "start": "wp-scripts start"
  },
  "files": [ "build", "src", "multi-columns.php" ],
  "devDependencies": {
    "@wordpress/scripts": "^30.6.0"
  }
}
/**
* NumberControl コンポーネント
* https://learn.wordpress.org/lesson/add-more-configuration-options/
*/

import { BaseControl } from "@wordpress/components";

const NumberControl = (props) => {
  const { min, max, step, value, onChange, label, ...additionalProps } = props;
  const minlength = min.toString().length;

  return (
    <BaseControl label={label}>
      <input
        type="number"
        min={min}
        max={max}
        step={step}
        value={value}
        onChange={(e) => {
          let val = e.target.value;
          if (e.target.value.length >= minlength) {
            val = val > max ? max : val;
            val = val < min ? min : val;
          }
          onChange(val);
        }}
        onBlur={(e) => {
          let val = e.target.value;
          if (e.target.value.length < minlength) {
            val = min;
          }
          onChange(val);
        }}
        style={{ marginLeft: "8px" }}
      />
    </BaseControl>
  );
};

export default NumberControl;

翻訳を作成

以下は上記で作成したプラグインの翻訳を自分で作成する方法です。

参考ページ

WP-CLI を使用して POT (Portable Object Template)ファイルと MO (Machine Object)ファイルを作成するので、インストールしていない場合はあらかじめインストールしておきます。

また、ブロックの翻訳ファイル(Javascript の翻訳関数による訳文)は JED 1.x JSON フォーマットにする必要がありますが、この JSON ファイルも WP-CLI で作成することができます。

languages ディレクトリ を作成

一般にすべての言語ファイルはプラグインの languages ディレクトリに配置するので、プラグインディレクトリ(この例の場合は multi-columns ディレクトリ)の直下に languages ディレクトリを作成します。

multi-columns ディレクトリでターミナルを開き、以下を実行します。

% mkdir languages

POT ファイルを作成

WP-CLI の wp i18n make-pot コマンドを使って POT ファイルを作成し、プロジェクトからすべての翻訳可能文字列を抽出します。

第1引数には 翻訳文字列抽出のためにスキャンするディレクトリを指定します。

この例では ./ を指定して、ルートディレクトリ以下のすべてのファイルを対象にし、--exclude オプションに src を指定して src ディレクトリを除外しています(この例の場合、src ディレクトリと build ディレクトリの翻訳は重複しているため)。

第2引数には、出力する POT ファイルのパス(ファイル名)を指定します。ファイル名は任意ですが、テキストドメイン名を使うのが一般的なようです。拡張子は .pot です。

% wp i18n make-pot ./ languages/multi-columns.pot --exclude=src

上記コマンドにより、languages ディレクトリに POT ファイル(multi-columns.pot)が生成されます。

以下は上記コマンドにより実際に生成された POT ファイルです。

JavaScript ファイル(build/index.js)に記述された翻訳テキストだけでなく、プラグインファイルのヘッダー情報や、block.json のいくつかのプロパティも翻訳用テキストとして抽出されています。

# Copyright (C) 2024 The WordPress Contributors
# This file is distributed under the GPL-2.0-or-later.
msgid ""
msgstr ""
"Project-Id-Version: Multi Columns 0.1.0\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/multi-columns\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2024-12-17T07:26:37+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.11.0\n"
"X-Domain: multi-columns\n"

#. Plugin Name of the plugin
#: multi-columns.php
msgid "Multi Columns"
msgstr ""

#. Description of the plugin
#: multi-columns.php
msgid "Custom Multi Columns block for newspaper style."
msgstr ""

#. Author of the plugin
#: multi-columns.php
msgid "The WordPress Contributors"
msgstr ""

#: build/index.js:1
msgid "Heading..."
msgstr ""

#: build/index.js:1
msgid "Sub-heading..."
msgstr ""

#: build/index.js:1
msgid "Separator color"
msgstr ""

#: build/index.js:1
msgid "Drop-Cap color"
msgstr ""

#: build/index.js:1
msgid "Column Settings"
msgstr ""

#: build/index.js:1
msgid "Columns"
msgstr ""

#: build/index.js:1
msgid "Width"
msgstr ""

#: build/index.js:1
msgid "Gap"
msgstr ""

#: build/index.js:1
msgid "Column Separator"
msgstr ""

#: build/index.js:1
msgid "Style"
msgstr ""

#: build/index.js:1
msgid "None"
msgstr ""

#: build/index.js:1
msgid "Solid"
msgstr ""

#: build/index.js:1
msgid "Dotted"
msgstr ""

#: build/index.js:1
msgid "Dashed"
msgstr ""

#: build/index.js:1
msgid "Double"
msgstr ""

#: build/index.js:1
msgid "Groove"
msgstr ""

#: build/index.js:1
msgid "Ridge"
msgstr ""

#: build/index.js:1
msgid "Drop-Cap"
msgstr ""

#: build/index.js:1
msgid "Size"
msgstr ""

#: build/index.js:1
msgid "Small"
msgstr ""

#: build/index.js:1
msgid "Large"
msgstr ""

#: build/index.js:1
msgid "Color settings"
msgstr ""

#: build/block.json
msgctxt "block title"
msgid "Multi Columns"
msgstr ""

#: build/block.json
msgctxt "block description"
msgid "Custom Multi Columns block for newspaper style."
msgstr ""

#: build/block.json
msgctxt "block keyword"
msgid "newspaper"
msgstr ""

#: build/block.json
msgctxt "block keyword"
msgid "columns"
msgstr ""

#: build/block.json
msgctxt "block keyword"
msgid "text"
msgstr ""

#: build/block.json
msgctxt "block style label"
msgid "Default"
msgstr ""

#: build/block.json
msgctxt "block style label"
msgid "Drop-Cap"
msgstr ""

msgid は翻訳対象の文字列で msgstr は翻訳文字列ですが、POT ファイルでは msgstr は常に空になっています。この POT ファイルを翻訳のテンプレートとして使い、PO ファイルと MO ファイルを作成します。

PO ファイルを作成

POT ファイルをコピーして PO ファイルを作成します。

PO ファイルのファイル名には言語コードを付けます。この例では日本語(ja)の翻訳なので「-ja」をファイル名に追加し、拡張子を「.po」にしてコピーします。

% cp languages/multi-columns.pot languages/multi-columns-ja.po

PO ファイルに翻訳を作成

エディターで PO ファイルを開いて、"Language: ja\n" の行を追加します(9行目)。

そして必要な msgstr に翻訳を追加します(空にすると、元のテキストがそのまま表示されます)。

# Copyright (C) 2024 The WordPress Contributors
# This file is distributed under the GPL-2.0-or-later.
msgid ""
msgstr ""
"Project-Id-Version: Multi Columns 0.1.0\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/multi-columns\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2024-12-17T07:26:37+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.11.0\n"
"X-Domain: multi-columns\n"

#. Plugin Name of the plugin
#: multi-columns.php
msgid "Multi Columns"
msgstr "マルチカラム"

#. Description of the plugin
#: multi-columns.php
msgid "Custom Multi Columns block for newspaper style."
msgstr "カスタムマルチカラムブロック"

#. Author of the plugin
#: multi-columns.php
msgid "The WordPress Contributors"
msgstr ""

#: build/index.js:1
msgid "Heading..."
msgstr "見出し"

#: build/index.js:1
msgid "Sub-heading..."
msgstr "小見出し"

#: build/index.js:1
msgid "Separator color"
msgstr "区切り線の色"

#: build/index.js:1
msgid "Drop-Cap color"
msgstr "ドロップキャップの色"

#: build/index.js:1
msgid "Column Settings"
msgstr "カラム設定"

#: build/index.js:1
msgid "Columns"
msgstr "カラム"

#: build/index.js:1
msgid "Width"
msgstr "幅"

#: build/index.js:1
msgid "Gap"
msgstr "間隔(ギャップ)"

#: build/index.js:1
msgid "Column Separator"
msgstr "カラム区切り線"

#: build/index.js:1
msgid "Style"
msgstr "スタイル"

#: build/index.js:1
msgid "None"
msgstr "なし"

#: build/index.js:1
msgid "Solid"
msgstr "ソリッド"

#: build/index.js:1
msgid "Dotted"
msgstr "ドット"

#: build/index.js:1
msgid "Dashed"
msgstr "ダッシュ"

#: build/index.js:1
msgid "Double"
msgstr "ダブル"

#: build/index.js:1
msgid "Groove"
msgstr "グルーブ"

#: build/index.js:1
msgid "Ridge"
msgstr "リッジ"

#: build/index.js:1
msgid "Drop-Cap"
msgstr "ドロップキャップ"

#: build/index.js:1
msgid "Size"
msgstr "サイズ"

#: build/index.js:1
msgid "Small"
msgstr "小"

#: build/index.js:1
msgid "Large"
msgstr "大"

#: build/index.js:1
msgid "Color settings"
msgstr "カラー設定"

#: build/block.json
msgctxt "block title"
msgid "Multi Columns"
msgstr "マルチカラム"

#: build/block.json
msgctxt "block description"
msgid "Custom Multi Columns block for newspaper style."
msgstr "カスタムマルチカラムブロック"

#: build/block.json
msgctxt "block keyword"
msgid "newspaper"
msgstr "新聞"

#: build/block.json
msgctxt "block keyword"
msgid "columns"
msgstr "カラム"

#: build/block.json
msgctxt "block keyword"
msgid "text"
msgstr "テキスト"

#: build/block.json
msgctxt "block style label"
msgid "Default"
msgstr "デフォルト"

#: build/block.json
msgctxt "block style label"
msgid "Drop-Cap"
msgstr "ドロップキャップ"

JSON フォーマットへの変換

WP-CLI の wp i18n make-json コマンドを使って PO ファイルから JSON ファイルを作成します。

wp i18n make-json に続けて PO ファイルのパス名(languages/multi-columns-ja.po)とオプションの --no-purge を指定します。--no-purge は元の PO ファイルから抽出された文字列を削除されないようにするために指定します。

% wp i18n make-json languages/multi-columns-ja.po --no-purge

または、以下のようにファイル名を省略してディレクトリのみを指定すると、languages ディレクトリから PO ファイルを検出して JSON ファイルが生成されます。

% wp i18n make-json languages/ --no-purge

上記を実行すると multi-columns-ja-{md5}.json という JSON ファイルが生成されます。{md5} の部分は翻訳対象の JavaScript ファイルの相対パスの MD5 ハッシュ値です。

追加で --pretty-print オプションを指定すると、以下のような見やすい形式で出力されます。

{
  "translation-revision-date": "YEAR-MO-DA HO:MI+ZONE",
  "generator": "WP-CLI/2.11.0",
  "source": "build/index.js",
  "domain": "messages",
  "locale_data": {
    "messages": {
      "": {
        "domain": "messages",
        "lang": "ja",
        "plural-forms": "nplurals=2; plural=(n != 1);"
      },
      "Heading...": ["\u898b\u51fa\u3057"],
      "Sub-heading...": ["\u5c0f\u898b\u51fa\u3057"],
      "Separator color": ["\u533a\u5207\u308a\u7dda\u306e\u8272"],
      "Drop-Cap color": [
        "\u30c9\u30ed\u30c3\u30d7\u30ad\u30e3\u30c3\u30d7\u306e\u8272"
      ],
      "Column Settings": ["\u30ab\u30e9\u30e0\u8a2d\u5b9a"],
      "Columns": ["\u30ab\u30e9\u30e0"],
      "Width": ["\u5e45"],
      "Gap": ["\u9593\u9694\uff08\u30ae\u30e3\u30c3\u30d7\uff09"],
      "Column Separator": ["\u30ab\u30e9\u30e0\u533a\u5207\u308a\u7dda"],
      "Style": ["\u30b9\u30bf\u30a4\u30eb"],
      "None": ["\u306a\u3057"],
      "Solid": ["\u30bd\u30ea\u30c3\u30c9"],
      "Dotted": ["\u30c9\u30c3\u30c8"],
      "Dashed": ["\u30c0\u30c3\u30b7\u30e5"],
      "Double": ["\u30c0\u30d6\u30eb"],
      "Groove": ["\u30b0\u30eb\u30fc\u30d6"],
      "Ridge": ["\u30ea\u30c3\u30b8"],
      "Drop-Cap": ["\u30c9\u30ed\u30c3\u30d7\u30ad\u30e3\u30c3\u30d7"],
      "Size": ["\u30b5\u30a4\u30ba"],
      "Small": ["\u5c0f"],
      "Large": ["\u5927"],
      "Color settings": ["\u30ab\u30e9\u30fc\u8a2d\u5b9a"]
    }
  }
}

MO ファイルを作成

wp i18n make-mo コマンドを使って MO ファイルを作成します。

% wp i18n make-mo languages/multi-columns-ja.po

wp i18n make-json コマンド同様、ファイル名を省略してディレクトリのみを指定すると、PO ファイルを検出して MO ファイルが生成されます。

% wp i18n make-mo languages/

MO ファイル multi-columns-ja.mo が生成されます。

MO ファイルは PHP の翻訳関数の訳文を表示させるためのファイルです。

※ WordPress 6.5 からは PHP ファイル(.l10n.php)があれば、MO ファイルの代わりに PHP ファイル(.l10n.php)を読み込むことでパフォーマンスが改善されるようになっています。

詳細:カスタムブロックの国際化対応(.l10n.php ファイルの作成)

MO ファイルをロード

プラグインファイル(multi-columns.php)の init フックで load_plugin_textdomain を使って MO ファイルをロードします。load_plugin_textdomain はプラグイン用の MO ファイルをロードする関数です。

第1引数にはテキストドメインを指定し、第3引数は MO ファイルの保存フォルダへのパスを指定します。第2引数はデフォルトの false のままで問題ありません。

第3引数には、.mo ファイルが存在するディレクトリへの相対パスを指定します。basename( dirname( __FILE__ ) ) は basename( __DIR__ ) と同じことでプラグインファイルあるディレクトリ名を返します。

function create_block_multi_columns_block_init() {
  //  MO ファイルをロード(追加)
  load_plugin_textdomain( 'multi-columns', false, basename( dirname( __FILE__ ) ) . '/languages' );

  register_block_type( __DIR__ . '/build' );
}
add_action('init', 'create_block_multi_columns_block_init');

JSON ファイルをロード

プラグインファイルの init フックで wp_set_script_translations を使って、WordPress に対して翻訳ファイル(JSON ファイル)がどこにあるかを伝えます(8行目)。

wp_set_script_translations() の第1引数には JavaScript のハンドル名を指定し、第2引数はテキストドメインを指定します。第3引数は JSON ファイルの保存フォルダへのパスを指定します。

function create_block_multi_columns_block_init() {
  //  MO ファイルをロード
  load_plugin_textdomain('multi-columns', false, basename(dirname(__FILE__)) . '/languages');

  register_block_type(__DIR__ . '/build');

  // JSON ファイルをロード(追加)
  wp_set_script_translations('create-block-multi-columns-editor-script', 'multi-columns', plugin_dir_path(__FILE__) . 'languages');
}
add_action('init', 'create_block_multi_columns_block_init');

この例の場合、JavaScript は block.json の editorScript に指定されている index.js ですが、プラグインファイル multi-columns.php で register_block_type() により自動的に登録されているため、ハンドル名の記述がありません。

ハンドル名は、block.json の name プロパティの「create-block/multi-columns」のスラッシュをハイフンに変えた文字列に、「-editor-script」を付けた値になります。

そのため、ハンドル名は create-block-multi-columns-editor-script になります(8行目)。

block.json の name プロパティは「名前空間/プラグイン名」となっています。

この例の場合、create-block コマンドでひな型を生成する際に namespace オプションを指定していないので、名前空間はデフォルトの「create-block」になっています。

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "create-block/multi-columns",

  ・・・中略・・・

  "textdomain": "multi-columns",
  "editorScript": "file:./index.js",
  "style": "file:./style-index.css"
}

前述のコードではハンドル名をハードコードしていますが、以下のように generate_block_asset_handle() 関数を使って editorScript のハンドル名を取得することができるので、以下に書き換えます。

generate_block_asset_handle() の第1引数にはブロック名を指定し、第2引数にはこの場合、editorScript を指定します(6行目)。

function create_block_multi_columns_block_init() {
  load_plugin_textdomain('multi-columns', false, basename(dirname(__FILE__)) . '/languages');
  register_block_type(__DIR__ . '/build');

  // editorScript のハンドル名を取得
  $script_handle = generate_block_asset_handle( 'create-block/multi-columns', 'editorScript' );

  // JSON ファイルをロード
  wp_set_script_translations($script_handle, 'multi-columns', plugin_dir_path(__FILE__) . 'languages');
}
add_action('init', 'create_block_multi_columns_block_init');

上記を保存してエディターを再読込すると、edit.js で翻訳テキストを取得する関数 __() を使用している部分(コントロールのラベルやプレースホルダー)が日本語になります(言語設定が日本語の場合)。

ブロックスタイルのラベル(Default と Drop-cap)も日本語になっています。こちらは MO ファイルとそのロード(load_plugin_textdomain)により翻訳されています。

また、プラグインページに表示される情報はプラグインファイル(PHP)の情報なので、MO ファイルが翻訳に使用されます。以下の例の場合、PO ファイルで「The WordPress Contributors」の翻訳を空のまま、指定していないので、その部分は翻訳されていません。

以下は最終的なプラグインファイルのコードです。ヘッダーコメントブロックに翻訳ファイルの場所を指定する Domain Path を追加しています(省略可能?)。

<?php

/**
* Plugin Name:       Multi Columns
* Description:       Custom Multi Columns block for newspaper style.
* Requires at least: 6.6
* Requires PHP:      7.2
* Version:           0.1.0
* Author:            The WordPress Contributors
* License:           GPL-2.0-or-later
* License URI:       https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain:       multi-columns
* Domain Path:       /languages
*
* @package CreateBlock
*/

if (! defined('ABSPATH')) {
  exit; // Exit if accessed directly.
}

function create_block_multi_columns_block_init() {

  load_plugin_textdomain('multi-columns', false, basename(dirname(__FILE__)) . '/languages');

  register_block_type(__DIR__ . '/build');

  $script_handle = generate_block_asset_handle( 'create-block/multi-columns', 'editorScript' );

  wp_set_script_translations($script_handle, 'multi-columns', plugin_dir_path(__FILE__) . 'languages');
}
add_action('init', 'create_block_multi_columns_block_init');