Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
c86c42f
add keyword functionality to icon search
n8finch Sep 2, 2026
7dd693f
add keyword functionality to icon block
n8finch Sep 2, 2026
10d0f53
update test
n8finch Sep 2, 2026
deb93b5
Update packages/icons/src/manifest.json
n8finch Sep 4, 2026
0c6e943
Update packages/icons/src/manifest.json
n8finch Sep 4, 2026
a858bbf
Update packages/icons/CHANGELOG.md
n8finch Sep 4, 2026
3d7148e
Update packages/icons/src/manifest.json
n8finch Sep 4, 2026
2083b56
Update packages/icons/src/manifest.json
n8finch Sep 4, 2026
aa0411b
resolve merge conflicts
n8finch Sep 4, 2026
70f7ea1
reject null keywords, match search Unicode-aware, and unregister test…
n8finch Sep 4, 2026
1366fd8
move storybook icon keywords to manifest
n8finch Sep 4, 2026
6baa88e
Assert on width/height attributes so stroke-width no longer trips the…
n8finch Sep 4, 2026
3e621b5
resolve merge conflicts
n8finch Sep 11, 2026
819c813
resolve merge conflicts
n8finch Sep 14, 2026
6892c91
Update lib/class-wp-rest-icons-controller-gutenberg.php
n8finch Sep 16, 2026
3e014f6
Merge branch 'trunk' into update/add-keyword-search-for-icons
n8finch Sep 16, 2026
b575ed8
remove UTF-8 arg
n8finch Sep 16, 2026
6ebcbc6
update keyword passthrough
n8finch Sep 16, 2026
5979bb6
Update lib/class-wp-icons-registry-gutenberg.php
n8finch Sep 17, 2026
d5181d2
Update lib/class-wp-icons-registry-gutenberg.php
n8finch Sep 17, 2026
7602735
Update lib/icons.php
n8finch Sep 17, 2026
6fa3c58
update changelog note
n8finch Sep 17, 2026
2d9ef93
resolve merge conflicts
n8finch Sep 17, 2026
8de3e8e
Merge branch 'trunk' into update/add-keyword-search-for-icons
t-hamano Sep 21, 2026
f7413d7
Tag Cloud: Add background and link colour support (#83264)
aaronrobertshaw Sep 21, 2026
535d5d0
Storybook: List every top-level folder in the sidebar order, design s…
juanmaguitar Sep 21, 2026
86b1397
Featured image field: support the `editor.PostFeaturedImage` filter i…
ntsekouras Sep 21, 2026
e7b0539
Storybook: Fold Foundations, DataViews, Fields, Admin UI and Icons un…
juanmaguitar Sep 21, 2026
efefb92
Block Editor: replace denylisted layout components in the Duotone fil…
mirka Sep 21, 2026
d61f8b2
Storybook: Group Widget Primitives, Widget Dashboard and Grid under W…
juanmaguitar Sep 21, 2026
a750737
Storybook: Group the editor UI under Editor (#82432)
juanmaguitar Sep 21, 2026
5df7118
Components: Deprecate Elevation (#83269)
mirka Sep 21, 2026
d89fbef
Post Time to Read: Add background image, size, and gradient support (…
aaronrobertshaw Sep 21, 2026
2e76d5a
Buttons: Add background image, size, and gradient support (#83203)
aaronrobertshaw Sep 21, 2026
703f25e
Storybook: Group component packages under Components (#82433)
juanmaguitar Sep 21, 2026
548d5f0
Terms List: Add background image, size, and gradient support (#83257)
aaronrobertshaw Sep 21, 2026
e6f552c
UI: Add SwitchControl component (#83146)
mirka Sep 21, 2026
ad5a320
Release tools: Move the plugin ZIP build out of bin/ (#83087)
manzoorwanijk Sep 21, 2026
71e550a
Storybook: Remove the legacy Tokens pages (#82434)
juanmaguitar Sep 21, 2026
10eaa2a
Storybook: Show the component status in the sidebar as status-* tags …
juanmaguitar Sep 21, 2026
0e67859
Storybook: Guard the set of top-level sidebar folders with a test (#8…
juanmaguitar Sep 21, 2026
e083d53
Storybook: Rewrite the introduction page as the front door of the des…
juanmaguitar Sep 21, 2026
a9cd388
Workflows: Refine and complete the push and pull_request branch filte…
desrosj Sep 21, 2026
7499eee
Merge branch 'trunk' into update/add-keyword-search-for-icons
n8finch Sep 21, 2026
03812d3
update changelogs, comments, types
n8finch Sep 21, 2026
97c2556
fix precision alignment
n8finch Sep 21, 2026
19ef827
change mb_stringpos to stringpos
n8finch Sep 21, 2026
81b49bf
update collection validation
n8finch Sep 22, 2026
5f6b358
resolve merge conflicts
n8finch Sep 22, 2026
32e43c6
add sanitize
n8finch Sep 22, 2026
7a41cf6
Merge branch 'trunk' of https://github.com/WordPress/gutenberg into u…
n8finch Sep 23, 2026
f8dfe8c
add backport changelog file
n8finch Sep 23, 2026
1a83934
resolve merge conflicts
n8finch Sep 23, 2026
7207f50
update tests
n8finch Sep 23, 2026
9b37909
remove check
n8finch Sep 23, 2026
2fac0a2
Update packages/icons/README.md
n8finch Sep 23, 2026
31007a0
Update packages/icons/CHANGELOG.md
n8finch Sep 23, 2026
cc21398
update tests
n8finch Sep 23, 2026
302d6f0
revert manifests and Storybook
n8finch Sep 24, 2026
3736e7f
resolve merge conflicts
n8finch Sep 24, 2026
3c9dc7d
resolve merge conflicts
n8finch Sep 25, 2026
5e5e662
update test setup
n8finch Sep 25, 2026
68d663d
resolve merge conflicts
n8finch Sep 28, 2026
6cb78f8
Update packages/icons/CHANGELOG.md
n8finch Sep 28, 2026
2926fbb
Apply batched suggestions from code review
n8finch Sep 28, 2026
8b950d9
Update packages/icons/README.md
n8finch Sep 28, 2026
6b0f64e
update alignment for comment
n8finch Sep 28, 2026
5738713
Merge branch 'trunk' of https://github.com/WordPress/gutenberg into u…
n8finch Sep 28, 2026
bde665a
Merge branch 'trunk' into update/add-keyword-search-for-icons
t-hamano Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backport-changelog/7.2/13672.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/13672

* https://github.com/WordPress/gutenberg/pull/82367
78 changes: 67 additions & 11 deletions lib/class-wp-icons-registry-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,13 @@ protected function __construct() {}
* @param array $icon_properties {
* List of properties for the icon.
*
* @type string $label Required. A human-readable label for the icon.
* @type string $content Optional. SVG markup for the icon.
* If not provided, the content will be retrieved from the `file_path` if set.
* If both `content` and `file_path` are not set, the icon will not be registered.
* @type string $file_path Optional. The full path to the file containing the icon content.
* @type string $label Required. A human-readable label for the icon.
* @type string $content Optional. SVG markup for the icon.
* If not provided, the content will be retrieved from the `file_path` if set.
* If both `content` and `file_path` are not set, the icon will not be registered.
* @type string $file_path Optional. The full path to the file containing the icon content.
* @type string[] $keywords Optional. Additional search terms for the icon, matched by
* `get_registered_icons()` alongside the name and label.
Comment thread
n8finch marked this conversation as resolved.
* }
* @return bool True if the icon was registered with success and false otherwise.
*/
Expand Down Expand Up @@ -154,7 +156,7 @@ public function register( $icon_name, $icon_properties ) {
return false;
}

$allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path' ), 1 );
$allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path', 'keywords' ), 1 );
foreach ( array_keys( $icon_properties ) as $key ) {
if ( ! array_key_exists( $key, $allowed_keys ) ) {
_doing_it_wrong(
Expand Down Expand Up @@ -192,6 +194,28 @@ public function register( $icon_name, $icon_properties ) {
return false;
}

if ( array_key_exists( 'keywords', $icon_properties ) ) {
if ( ! is_array( $icon_properties['keywords'] ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Icon keywords must be an array of strings.', 'gutenberg' ),
'7.2.0'
);
return false;
}

foreach ( $icon_properties['keywords'] as $keyword ) {
if ( ! is_string( $keyword ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Icon keywords must be an array of strings.', 'gutenberg' ),
'7.2.0'
);
return false;
}
}
}

if (
( ! isset( $icon_properties['content'] ) && ! isset( $icon_properties['file_path'] ) ) ||
( isset( $icon_properties['content'] ) && isset( $icon_properties['file_path'] ) )
Expand Down Expand Up @@ -338,16 +362,45 @@ protected function get_content( $icon_name ) {
}

/**
* Modified to also search in icon labels
* Determines whether an icon matches a search term.
*
* The term is matched case-insensitively against the icon's name, its label,
* and any of its keywords.
*
* @param array $icon Registered icon properties.
* @param string $search Search term.
* @return bool True if the icon matches the search term, false otherwise.
*/
protected function icon_matches_search( $icon, $search ) {
if ( false !== stripos( $icon['name'], $search ) ) {
return true;
}

if ( false !== stripos( $icon['label'], $search ) ) {
return true;
}

foreach ( $icon['keywords'] ?? array() as $keyword ) {
if ( false !== stripos( $keyword, $search ) ) {
return true;
}
}

return false;
}

/**
* Modified to also search in icon labels and keywords.
*
* @param string $search Optional. Search term matched against each icon's name,
* label, and keywords. Default empty string, which returns
* every registered icon.
*/
public function get_registered_icons( $search = '' ) {
$icons = array();

foreach ( $this->registered_icons as $icon ) {
if ( ! empty( $search )
&& false === stripos( $icon['name'], $search )
&& false === stripos( $icon['label'], $search )
) {
if ( ! empty( $search ) && ! $this->icon_matches_search( $icon, $search ) ) {
continue;
}

Expand Down Expand Up @@ -384,6 +437,9 @@ public static function get_instance() {
} else {
continue;
}
if ( ! empty( $icon['keywords'] ) ) {
$icon_properties['keywords'] = $icon['keywords'];
}
$gutenberg_registry->register( $icon['name'], $icon_properties );
}
}
Expand Down
18 changes: 16 additions & 2 deletions lib/class-wp-rest-icons-controller-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ public function get_icon( $name ) {
/**
* Prepare a raw icon before it gets output in a REST API response.
*
* Adds a `collection` field to the base response while keeping the
* namespaced icon name (e.g. `core/arrow-left`) as the `name` field.
* Adds `collection` and `keywords` fields to the base response while keeping
* the namespaced icon name (e.g. `core/arrow-left`) as the `name` field.
*
* @param array $item Raw icon as registered.
* @param WP_REST_Request $request Request object.
Expand All @@ -183,6 +183,12 @@ public function prepare_item_for_response( $item, $request ) {
$response->set_data( $data );
}

if ( rest_is_field_included( 'keywords', $fields ) ) {
$data = $response->get_data();
$data['keywords'] = isset( $item['keywords'] ) ? array_values( $item['keywords'] ) : array();
$response->set_data( $data );
}

return $response;
}

Expand All @@ -205,6 +211,14 @@ public function get_item_schema() {
'context' => array( 'view', 'edit', 'embed' ),
);

$schema['properties']['keywords'] = array(
'description' => __( 'Additional search terms for the icon.', 'gutenberg' ),
'type' => 'array',
'items' => array( 'type' => 'string' ),
'readonly' => true,
'context' => array( 'view', 'edit', 'embed' ),
);
Comment thread
n8finch marked this conversation as resolved.

$this->schema = $schema;

return $this->add_additional_fields_schema( $this->schema );
Expand Down
12 changes: 7 additions & 5 deletions lib/compat/wordpress-7.1/icons.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ function wp_unregister_icon_collection( $slug ) {
* @param array $args {
* List of properties for the icon.
*
* @type string $label Required. A human-readable label for the icon.
* @type string $content Optional. SVG markup for the icon.
* If not provided, the content will be retrieved from the `file_path` if set.
* If both `content` and `file_path` are not set, the icon will not be registered.
* @type string $file_path Optional. The full path to the file containing the icon content.
* @type string $label Required. A human-readable label for the icon.
* @type string $content Optional. SVG markup for the icon.
* If not provided, the content will be retrieved from the `file_path` if set.
* If both `content` and `file_path` are not set, the icon will not be registered.
* @type string $file_path Optional. The full path to the file containing the icon content.
* @type string[] $keywords Optional. Additional search terms for the icon, matched by
* `get_registered_icons()` alongside the name and label.
* }
* @return bool True if the icon was registered successfully, else false.
*/
Expand Down
4 changes: 4 additions & 0 deletions lib/icons.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ function gutenberg_register_default_icons() {
'file_path' => $icons_directory . $icon_data['filePath'],
);

if ( isset( $icon_data['keywords'] ) ) {
$icon_args['keywords'] = $icon_data['keywords'];
}

foreach ( $icon_data['collections'] as $collection_slug ) {
wp_register_icon( $collection_slug . '/' . $icon_name, $icon_args );
}
Expand Down
1 change: 1 addition & 0 deletions packages/block-library/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Columns: Remove the column count slider from the block settings ([#83262](https://github.com/WordPress/gutenberg/pull/83262)).
- Media & Text: Lower the specificity of the content area's default padding so themes can override it, including through the block's custom CSS in `theme.json` ([#83563](https://github.com/WordPress/gutenberg/pull/83563)).
- Gallery: Add an "Order by" sorting control to the static gallery mode, and consolidate sorting control for static and dynamic modes in the Settings panel ([#83438](https://github.com/WordPress/gutenberg/pull/83438)).
- Icon: Match an icon's keywords as well as its name and label when searching the icon library ([#82367](https://github.com/WordPress/gutenberg/pull/82367)).

### Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ export default function CustomInserterModal( { onClose, value, onChange } ) {
const iconLabel = normalizeSearchInput( icon.label );

return (
iconName.includes( input ) || iconLabel.includes( input )
iconName.includes( input ) ||
iconLabel.includes( input ) ||
( icon.keywords ?? [] ).some( ( keyword ) =>
normalizeSearchInput( keyword ).includes( input )
)
);
} );
}
Expand Down
4 changes: 4 additions & 0 deletions packages/core-data/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Enhancements

- `Icon`: add the `keywords` field exposed by the icons REST endpoint ([#82367](https://github.com/WordPress/gutenberg/pull/82367)).

### Internal

- Added an internal entity sync interface: `registerEntitySyncManager` and `getEntitySyncManager` let one manager plug into the entity lifecycle (record loads, edits, saves, deletes, and undo). The real-time collaboration experiment now runs through it instead of being called directly by the store. The interface is not exposed outside the package yet ([#83410](https://github.com/WordPress/gutenberg/pull/83410)).
Expand Down
4 changes: 4 additions & 0 deletions packages/core-data/src/entity-types/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ declare module './base-entity-records' {
* The slug of the collection this icon belongs to.
*/
collection?: string;
/**
* Additional search terms for the icon.
*/
keywords?: string[];
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/icons/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- `formatBold`: draw the B with heavier vertical strokes so it reads as bold at a glance ([#83321](https://github.com/WordPress/gutenberg/pull/83321)).
- `justifySpaceBetween` and `justifySpaceEvenly`: redraw so the two icons share one construction with the new `justifySpaceAround`, differing only in where the blocks sit ([#83519](https://github.com/WordPress/gutenberg/pull/83519)).
- `headingLevel1` to `headingLevel6`: enlarge the drawings to improve their optical balance with the rest of the set ([#83268](https://github.com/WordPress/gutenberg/pull/83268)).
- `manifest.json`: Add optional `keywords` property to improve discoverability. ([#82367](https://github.com/WordPress/gutenberg/pull/82367))

### Bug Fixes

Expand Down Expand Up @@ -124,7 +125,7 @@

### Breaking Changes

- Rename Tabs-related icons: `tabs-menu` to `tab-list`, `tabs-menu-item` to `tab`, and `tab` to `tab-panel`. ([#77418](https://github.com/WordPress/gutenberg/pull/77418))
- Rename Tabs-related icons: `tabs-menu` to `tab-list`, `tabs-menu-item` to `tab`, and `tab` to `tab-panel`. ([#77418](https://github.com/WordPress/gutenberg/pull/77418))

## 12.2.0 (2026-04-15)

Expand Down
2 changes: 1 addition & 1 deletion packages/icons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ To add a new icon to the library, follow these steps:
- `collections` (optional): The collections the icon is registered in once it ships to WordPress Core. Omit it to keep the icon in the JS library only. **Important**: Once an icon ships to WordPress Core, removing it is difficult, so carefully consider this field before setting it. The available collections are:
- `core`: the public collection. Its icons are exposed through the SVG Icons API, which makes them selectable in the Icon block.
- `core-admin`: the collection the WordPress admin interface renders from. It is not public, so its icons are never exposed through the REST API or selectable in the Icon block, and unregistering `core` cannot take them away from the admin interface.

- `keywords` (optional): An array of additional translatable search terms for the icon (e.g. `[ "hamburger", "navigation" ]` for a Menu icon).
4. **Do not edit `manifest.php`**: The `manifest.php` file is automatically generated from `manifest.json` by the build script. Do not edit it manually, as your changes will be overwritten when the build runs.

After adding your icon, run `npm run build` to generate the TypeScript files and update `manifest.php`.
Expand Down
15 changes: 14 additions & 1 deletion packages/icons/lib/generate-manifest-php.cjs
Comment thread
n8finch marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,23 @@ function generatePHPArray( manifest ) {
.map( ( collection ) => `'${ escapePHPString( collection ) }'` )
.join( ', ' );

// Keywords are optional, and are localized like labels since they are
// matched against user-entered search terms.
const keywords = item.keywords?.length
? `\n\t\t'keywords' => array( ${ item.keywords
.map(
( keyword ) =>
`_x( '${ escapePHPString(
keyword
) }', 'icon keyword', 'gutenberg' )`
)
.join( ', ' ) } ),`
: '';

return `${ key } => array(
'label' => _x( '${ label }', 'icon label', 'gutenberg' ),
'filePath' => '${ filePath }',
'collections' => array( ${ collections } ),
'collections' => array( ${ collections } ),${ keywords }
),`;
} );

Expand Down
20 changes: 20 additions & 0 deletions packages/icons/lib/validate-collection.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function isStrokeBasedSvg( svgContent ) {
* - Each manifest entry has a matching SVG in library/, and vice versa.
* - Each manifest entry's `collections` property, if present, is a non-empty array of
* known collection slugs.
* - Each manifest entry's `keywords` property, if present, is an array of strings.
* - Each SVG uses currentColor so icons inherit text color.
* - Each SVG uses viewBox="0 0 24 24".
* - Each stroke-based SVG contains at least one stroked graphical element.
Expand Down Expand Up @@ -90,6 +91,25 @@ async function validateCollection() {
}
}

/*
* Verify that `keywords`, if present, is an array of strings.
*/
if (
'keywords' in icon &&
( ! Array.isArray( icon.keywords ) ||
icon.keywords.some(
( keyword ) => typeof keyword !== 'string'
) )
) {
problems.push(
`- Invalid icon definition for icon '${
icon.slug
}': expected 'keywords' to be an array of strings, saw ${ JSON.stringify(
icon.keywords
) }`
);
}

/*
* Verify that the corresponding SVG file is found.
*/
Expand Down
39 changes: 28 additions & 11 deletions phpunit/class-wp-icon-collections-registry-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,48 @@ class WP_Test_Icon_Collections_Registry extends WP_UnitTestCase {
*/
protected $collections;

/**
* Icons registry instance in place before the test, restored in tear_down.
*
* @var WP_Icons_Registry|null
*/
private $original_icons_registry = null;

public function set_up() {
parent::set_up();
$this->collections = WP_Icon_Collections_Registry::get_instance();
}

public function tear_down() {
foreach ( array( 'plugin-a', 'plugin-b', 'my-collection' ) as $slug ) {
if ( $this->collections->is_registered( $slug ) ) {
$this->collections->unregister( $slug );
}
}

$instance_property = new ReflectionProperty( WP_Icons_Registry_Gutenberg::class, 'instance' );

/*
* Start from a clean registry, keeping the instance that was in place so
* `tear_down()` can put it back rather than leaving later suites with a
* base registry that rejects Gutenberg-only icon properties.
*
* ReflectionProperty::setAccessible is:
* - redundant as of 8.1.0, which made all properties accessible
* - deprecated as of 8.5.0
* - needed until 8.1.0, as property `instance` is private
*/
$instance_property = new ReflectionProperty( WP_Icons_Registry_Gutenberg::class, 'instance' );
if ( PHP_VERSION_ID < 80100 ) {
$instance_property->setAccessible( true );
}

$this->original_icons_registry = $instance_property->getValue();
$instance_property->setValue( null, null );
}

public function tear_down() {
$instance_property = new ReflectionProperty( WP_Icons_Registry_Gutenberg::class, 'instance' );
if ( PHP_VERSION_ID < 80100 ) {
$instance_property->setAccessible( true );
}
$instance_property->setValue( null, $this->original_icons_registry );
$this->original_icons_registry = null;

foreach ( array( 'plugin-a', 'plugin-b', 'my-collection' ) as $slug ) {
if ( $this->collections->is_registered( $slug ) ) {
$this->collections->unregister( $slug );
}
}

parent::tear_down();
}
Expand Down
Loading
Loading