Skip to content

Conversation

getdave
Copy link
Contributor

@getdave getdave commented Aug 5, 2025

What

Restricts RichText formatting controls when in write mode (contentOnly) to provide a simpler, more focused editing experience while maintaining essential content functionality. Introduces a private essentialFormatKey Symbol for format registration to distinguish between essential and non-essential formats.

Why

Write mode should be as simple as possible to help users focus on content creation without distractions from advanced formatting options. Currently, users see all formatting controls including strikethrough, highlight, and other advanced options even in write mode. However, links are essential content functionality and should remain available in Write Mode, especially for Navigation blocks.

How

  • Write Mode detection in RichText component - Added Write Mode detection with experiment flag check in the main RichText component
  • Centralized format filtering - Enhanced useFormatTypes hook to filter formats based on Write Mode and essential status
  • Single store subscription - Uses one centralized subscription for Write Mode detection instead of multiple per format component
  • Introduced private essentialFormatKey Symbol for format registration (using WordPress Core's private API pattern)
  • By default, all formats are non-essential except for hardcoded essential formats: bold, italic, and link
  • Third-party formats are non-essential by default to prevent them from cluttering the Write Mode toolbar
  • Formats can opt-in to be essential by setting [ essentialFormatKey ]: true in their registration
  • Only affects Write Mode when experiment is active - early return when window.__experimentalEditorWriteMode is false ensures minimal overhead
  • In write mode: Shows only essential formats (bold, italic, link + any formats marked as essential)
  • In regular mode: Shows full formatting toolbar (all available formats)

Testing

  1. Enable Write Mode experiment in Gutenberg > Experiments
  2. Switch to write mode in the editor
  3. Select text in a RichText block (paragraph, heading, etc.)
  4. Verify bold, italic, and link buttons are visible in the inline formatting toolbar
  5. Verify non-essential formats (strikethrough, highlight, etc.) are hidden
  6. Test link functionality in Write Mode works correctly
  7. Switch back to regular mode and verify full formatting toolbar reappears
  8. Verify performance is optimal when Write Mode experiment is disabled

Testing Format Registration

You can test the essential format registration by applying the following diff to make strikethrough essential:

--- a/packages/format-library/src/strikethrough/index.js
+++ b/packages/format-library/src/strikethrough/index.js
@@ -1,6 +1,7 @@
 /**
  * WordPress dependencies
  */
+import { unlock } from '../lock-unlock';
 import { __ } from '@wordpress/i18n';
 import { toggleFormat } from '@wordpress/rich-text';
 import {
@@ -8,6 +9,9 @@ import {
 	RichTextShortcut,
 	__unstableRichTextInputEvent,
 	privateApis as blockEditorPrivateApis,
 } from '@wordpress/block-editor';
 import { formatStrikethrough } from '@wordpress/icons';
 
+const { essentialFormatKey } = unlock( blockEditorPrivateApis );
+
 const name = 'core/strikethrough';
 const title = __( 'Strikethrough' );
 
@@ -15,6 +19,7 @@ export const strikethrough = {
 	name,
 	title,
 	tagName: 's',
+	[ essentialFormatKey ]: true,
 	className: null,
 	edit( { isActive, value, onChange, onFocus } ) {

After applying this diff:

  1. Build the packages if needed
  2. Enable Write Mode experiment
  3. Switch to Write Mode
  4. Verify that strikethrough now appears in the formatting toolbar (in the dropdown).
  5. Verify that other non-essential formats remain hidden

Screenshots

Screen Shot 2025-08-07 at 11 40 23

Breaking Changes

None - this is a UI enhancement that maintains all existing functionality in regular editing mode.

Performance Considerations

  • Zero overhead when Write Mode experiment is disabled - Filtering logic is bypassed entirely
  • Single store subscription - Uses one centralized subscription for Write Mode detection
  • Centralized filtering - All format filtering logic is handled in useFormatTypes hook
  • Efficient memoization - Uses React's built-in memoization for optimal re-rendering
  • Early return optimization - Bypasses filtering logic when experiment is disabled

API Considerations

  • essentialFormatKey is a private Symbol following WordPress Core's private API pattern
  • Third-party formats are non-essential by default to prevent toolbar clutter
  • Formats can opt-in to be essential by using the private Symbol in their registration
  • Private API ensures no public API commitment while allowing Core packages to use the feature

Related

This change supports the Navigation Block Write Mode improvements (see #70977) where navigation blocks need to be editable in Write Mode with link functionality available.

- Add useBlockEditingMode() hook to detect write mode
- In contentOnly mode, only show bold and italic controls
- Remove link and unknown formats from write mode toolbar
- Hide 'More' dropdown with additional formatting options in write mode
- Keep write mode simple and focused on essential content editing
@getdave getdave requested a review from ellatrix as a code owner August 5, 2025 09:46
Copy link

github-actions bot commented Aug 5, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: getdave <get_dave@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: scruffian <scruffian@git.wordpress.org>
Co-authored-by: jeryj <jeryj@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@getdave getdave self-assigned this Aug 5, 2025
@getdave getdave requested review from scruffian and richtabor August 5, 2025 09:48
@getdave getdave added [Package] Format library /packages/format-library [Feature] Write mode [Type] Enhancement A suggestion for improvement. labels Aug 5, 2025
Copy link

github-actions bot commented Aug 5, 2025

Size Change: +175 B (+0.01%)

Total Size: 1.91 MB

Filename Size Change
build/block-editor/index.min.js 265 kB +115 B (+0.04%)
build/format-library/index.min.js 8.23 kB +60 B (+0.73%)
ℹ️ View Unchanged
Filename Size
build-module/a11y/index.min.js 482 B
build-module/block-library/file/view.min.js 466 B
build-module/block-library/form/view.min.js 533 B
build-module/block-library/image/view.min.js 1.78 kB
build-module/block-library/navigation/view.min.js 1.19 kB
build-module/block-library/query/view.min.js 767 B
build-module/block-library/search/view.min.js 639 B
build-module/interactivity-router/full-page.min.js 565 B
build-module/interactivity-router/index.min.js 11.4 kB
build-module/interactivity/debug.min.js 17.5 kB
build-module/interactivity/index.min.js 13.9 kB
build/a11y/index.min.js 952 B
build/annotations/index.min.js 2.13 kB
build/api-fetch/index.min.js 2.4 kB
build/autop/index.min.js 2.12 kB
build/blob/index.min.js 579 B
build/block-directory/index.min.js 7.18 kB
build/block-directory/style-rtl.css 1.03 kB
build/block-directory/style.css 1.03 kB
build/block-editor/content-rtl.css 4.43 kB
build/block-editor/content.css 4.42 kB
build/block-editor/default-editor-styles-rtl.css 392 B
build/block-editor/default-editor-styles.css 392 B
build/block-editor/style-rtl.css 15.9 kB
build/block-editor/style.css 15.9 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 61 B
build/block-library/blocks/archives/style-rtl.css 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 149 B
build/block-library/blocks/audio/editor.css 151 B
build/block-library/blocks/audio/style-rtl.css 132 B
build/block-library/blocks/audio/style.css 132 B
build/block-library/blocks/audio/theme-rtl.css 134 B
build/block-library/blocks/audio/theme.css 134 B
build/block-library/blocks/avatar/editor-rtl.css 115 B
build/block-library/blocks/avatar/editor.css 115 B
build/block-library/blocks/avatar/style-rtl.css 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/button/editor-rtl.css 265 B
build/block-library/blocks/button/editor.css 265 B
build/block-library/blocks/button/style-rtl.css 554 B
build/block-library/blocks/button/style.css 554 B
build/block-library/blocks/buttons/editor-rtl.css 291 B
build/block-library/blocks/buttons/editor.css 291 B
build/block-library/blocks/buttons/style-rtl.css 349 B
build/block-library/blocks/buttons/style.css 349 B
build/block-library/blocks/calendar/style-rtl.css 239 B
build/block-library/blocks/calendar/style.css 239 B
build/block-library/blocks/categories/editor-rtl.css 132 B
build/block-library/blocks/categories/editor.css 131 B
build/block-library/blocks/categories/style-rtl.css 152 B
build/block-library/blocks/categories/style.css 152 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 139 B
build/block-library/blocks/code/style.css 139 B
build/block-library/blocks/code/theme-rtl.css 122 B
build/block-library/blocks/code/theme.css 122 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 420 B
build/block-library/blocks/columns/style.css 420 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 124 B
build/block-library/blocks/comment-author-avatar/editor.css 124 B
build/block-library/blocks/comment-author-name/style-rtl.css 72 B
build/block-library/blocks/comment-author-name/style.css 72 B
build/block-library/blocks/comment-content/style-rtl.css 120 B
build/block-library/blocks/comment-content/style.css 120 B
build/block-library/blocks/comment-date/style-rtl.css 65 B
build/block-library/blocks/comment-date/style.css 65 B
build/block-library/blocks/comment-edit-link/style-rtl.css 70 B
build/block-library/blocks/comment-edit-link/style.css 70 B
build/block-library/blocks/comment-reply-link/style-rtl.css 71 B
build/block-library/blocks/comment-reply-link/style.css 71 B
build/block-library/blocks/comment-template/style-rtl.css 191 B
build/block-library/blocks/comment-template/style.css 191 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 168 B
build/block-library/blocks/comments-pagination/editor.css 168 B
build/block-library/blocks/comments-pagination/style-rtl.css 201 B
build/block-library/blocks/comments-pagination/style.css 201 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 842 B
build/block-library/blocks/comments/editor.css 842 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 637 B
build/block-library/blocks/cover/editor-rtl.css 631 B
build/block-library/blocks/cover/editor.css 631 B
build/block-library/blocks/cover/style-rtl.css 1.7 kB
build/block-library/blocks/cover/style.css 1.69 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 86 B
build/block-library/blocks/details/style.css 86 B
build/block-library/blocks/embed/editor-rtl.css 331 B
build/block-library/blocks/embed/editor.css 331 B
build/block-library/blocks/embed/style-rtl.css 419 B
build/block-library/blocks/embed/style.css 419 B
build/block-library/blocks/embed/theme-rtl.css 133 B
build/block-library/blocks/embed/theme.css 133 B
build/block-library/blocks/file/editor-rtl.css 326 B
build/block-library/blocks/file/editor.css 326 B
build/block-library/blocks/file/style-rtl.css 278 B
build/block-library/blocks/file/style.css 278 B
build/block-library/blocks/footnotes/style-rtl.css 198 B
build/block-library/blocks/footnotes/style.css 197 B
build/block-library/blocks/form-input/editor-rtl.css 229 B
build/block-library/blocks/form-input/editor.css 229 B
build/block-library/blocks/form-input/style-rtl.css 349 B
build/block-library/blocks/form-input/style.css 349 B
build/block-library/blocks/form-submission-notification/editor-rtl.css 344 B
build/block-library/blocks/form-submission-notification/editor.css 341 B
build/block-library/blocks/form-submit-button/style-rtl.css 69 B
build/block-library/blocks/form-submit-button/style.css 69 B
build/block-library/blocks/freeform/editor-rtl.css 2.59 kB
build/block-library/blocks/freeform/editor.css 2.59 kB
build/block-library/blocks/gallery/editor-rtl.css 615 B
build/block-library/blocks/gallery/editor.css 616 B
build/block-library/blocks/gallery/style-rtl.css 1.83 kB
build/block-library/blocks/gallery/style.css 1.83 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 334 B
build/block-library/blocks/group/editor.css 334 B
build/block-library/blocks/group/style-rtl.css 103 B
build/block-library/blocks/group/style.css 103 B
build/block-library/blocks/group/theme-rtl.css 79 B
build/block-library/blocks/group/theme.css 79 B
build/block-library/blocks/heading/style-rtl.css 188 B
build/block-library/blocks/heading/style.css 188 B
build/block-library/blocks/html/editor-rtl.css 353 B
build/block-library/blocks/html/editor.css 354 B
build/block-library/blocks/image/editor-rtl.css 763 B
build/block-library/blocks/image/editor.css 763 B
build/block-library/blocks/image/style-rtl.css 1.6 kB
build/block-library/blocks/image/style.css 1.59 kB
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/latest-comments/style-rtl.css 355 B
build/block-library/blocks/latest-comments/style.css 354 B
build/block-library/blocks/latest-posts/editor-rtl.css 139 B
build/block-library/blocks/latest-posts/editor.css 138 B
build/block-library/blocks/latest-posts/style-rtl.css 520 B
build/block-library/blocks/latest-posts/style.css 520 B
build/block-library/blocks/list/style-rtl.css 107 B
build/block-library/blocks/list/style.css 107 B
build/block-library/blocks/loginout/style-rtl.css 61 B
build/block-library/blocks/loginout/style.css 61 B
build/block-library/blocks/media-text/editor-rtl.css 321 B
build/block-library/blocks/media-text/editor.css 320 B
build/block-library/blocks/media-text/style-rtl.css 543 B
build/block-library/blocks/media-text/style.css 542 B
build/block-library/blocks/more/editor-rtl.css 393 B
build/block-library/blocks/more/editor.css 393 B
build/block-library/blocks/navigation-link/editor-rtl.css 566 B
build/block-library/blocks/navigation-link/editor.css 568 B
build/block-library/blocks/navigation-link/style-rtl.css 192 B
build/block-library/blocks/navigation-link/style.css 191 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 295 B
build/block-library/blocks/navigation-submenu/editor.css 294 B
build/block-library/blocks/navigation/editor-rtl.css 2.23 kB
build/block-library/blocks/navigation/editor.css 2.24 kB
build/block-library/blocks/navigation/style-rtl.css 2.27 kB
build/block-library/blocks/navigation/style.css 2.26 kB
build/block-library/blocks/nextpage/editor-rtl.css 392 B
build/block-library/blocks/nextpage/editor.css 392 B
build/block-library/blocks/page-list/editor-rtl.css 356 B
build/block-library/blocks/page-list/editor.css 356 B
build/block-library/blocks/page-list/style-rtl.css 192 B
build/block-library/blocks/page-list/style.css 192 B
build/block-library/blocks/paragraph/editor-rtl.css 251 B
build/block-library/blocks/paragraph/editor.css 251 B
build/block-library/blocks/paragraph/style-rtl.css 341 B
build/block-library/blocks/paragraph/style.css 340 B
build/block-library/blocks/post-author-biography/style-rtl.css 74 B
build/block-library/blocks/post-author-biography/style.css 74 B
build/block-library/blocks/post-author-name/style-rtl.css 69 B
build/block-library/blocks/post-author-name/style.css 69 B
build/block-library/blocks/post-author/style-rtl.css 188 B
build/block-library/blocks/post-author/style.css 189 B
build/block-library/blocks/post-comments-count/style-rtl.css 72 B
build/block-library/blocks/post-comments-count/style.css 72 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 527 B
build/block-library/blocks/post-comments-form/style.css 528 B
build/block-library/blocks/post-comments-link/style-rtl.css 71 B
build/block-library/blocks/post-comments-link/style.css 71 B
build/block-library/blocks/post-content/style-rtl.css 61 B
build/block-library/blocks/post-content/style.css 61 B
build/block-library/blocks/post-date/style-rtl.css 62 B
build/block-library/blocks/post-date/style.css 62 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 155 B
build/block-library/blocks/post-excerpt/style.css 155 B
build/block-library/blocks/post-featured-image/editor-rtl.css 715 B
build/block-library/blocks/post-featured-image/editor.css 712 B
build/block-library/blocks/post-featured-image/style-rtl.css 347 B
build/block-library/blocks/post-featured-image/style.css 347 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 B
build/block-library/blocks/post-template/style-rtl.css 414 B
build/block-library/blocks/post-template/style.css 414 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 70 B
build/block-library/blocks/post-time-to-read/style.css 70 B
build/block-library/blocks/post-title/style-rtl.css 162 B
build/block-library/blocks/post-title/style.css 162 B
build/block-library/blocks/preformatted/style-rtl.css 125 B
build/block-library/blocks/preformatted/style.css 125 B
build/block-library/blocks/pullquote/editor-rtl.css 133 B
build/block-library/blocks/pullquote/editor.css 133 B
build/block-library/blocks/pullquote/style-rtl.css 365 B
build/block-library/blocks/pullquote/style.css 365 B
build/block-library/blocks/pullquote/theme-rtl.css 176 B
build/block-library/blocks/pullquote/theme.css 176 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 121 B
build/block-library/blocks/query-pagination-numbers/editor.css 118 B
build/block-library/blocks/query-pagination/editor-rtl.css 154 B
build/block-library/blocks/query-pagination/editor.css 154 B
build/block-library/blocks/query-pagination/style-rtl.css 237 B
build/block-library/blocks/query-pagination/style.css 237 B
build/block-library/blocks/query-title/style-rtl.css 64 B
build/block-library/blocks/query-title/style.css 64 B
build/block-library/blocks/query-total/style-rtl.css 64 B
build/block-library/blocks/query-total/style.css 64 B
build/block-library/blocks/query/editor-rtl.css 404 B
build/block-library/blocks/query/editor.css 404 B
build/block-library/blocks/quote/style-rtl.css 238 B
build/block-library/blocks/quote/style.css 238 B
build/block-library/blocks/quote/theme-rtl.css 233 B
build/block-library/blocks/quote/theme.css 236 B
build/block-library/blocks/read-more/style-rtl.css 131 B
build/block-library/blocks/read-more/style.css 131 B
build/block-library/blocks/rss/editor-rtl.css 126 B
build/block-library/blocks/rss/editor.css 126 B
build/block-library/blocks/rss/style-rtl.css 284 B
build/block-library/blocks/rss/style.css 283 B
build/block-library/blocks/search/editor-rtl.css 199 B
build/block-library/blocks/search/editor.css 199 B
build/block-library/blocks/search/style-rtl.css 674 B
build/block-library/blocks/search/style.css 671 B
build/block-library/blocks/search/theme-rtl.css 113 B
build/block-library/blocks/search/theme.css 113 B
build/block-library/blocks/separator/editor-rtl.css 100 B
build/block-library/blocks/separator/editor.css 100 B
build/block-library/blocks/separator/style-rtl.css 248 B
build/block-library/blocks/separator/style.css 248 B
build/block-library/blocks/separator/theme-rtl.css 195 B
build/block-library/blocks/separator/theme.css 195 B
build/block-library/blocks/shortcode/editor-rtl.css 286 B
build/block-library/blocks/shortcode/editor.css 286 B
build/block-library/blocks/site-logo/editor-rtl.css 773 B
build/block-library/blocks/site-logo/editor.css 770 B
build/block-library/blocks/site-logo/style-rtl.css 218 B
build/block-library/blocks/site-logo/style.css 218 B
build/block-library/blocks/site-tagline/editor-rtl.css 87 B
build/block-library/blocks/site-tagline/editor.css 87 B
build/block-library/blocks/site-tagline/style-rtl.css 65 B
build/block-library/blocks/site-tagline/style.css 65 B
build/block-library/blocks/site-title/editor-rtl.css 85 B
build/block-library/blocks/site-title/editor.css 85 B
build/block-library/blocks/site-title/style-rtl.css 143 B
build/block-library/blocks/site-title/style.css 143 B
build/block-library/blocks/social-link/editor-rtl.css 314 B
build/block-library/blocks/social-link/editor.css 314 B
build/block-library/blocks/social-links/editor-rtl.css 339 B
build/block-library/blocks/social-links/editor.css 338 B
build/block-library/blocks/social-links/style-rtl.css 1.51 kB
build/block-library/blocks/social-links/style.css 1.51 kB
build/block-library/blocks/spacer/editor-rtl.css 346 B
build/block-library/blocks/spacer/editor.css 346 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table-of-contents/style-rtl.css 83 B
build/block-library/blocks/table-of-contents/style.css 83 B
build/block-library/blocks/table/editor-rtl.css 394 B
build/block-library/blocks/table/editor.css 394 B
build/block-library/blocks/table/style-rtl.css 640 B
build/block-library/blocks/table/style.css 639 B
build/block-library/blocks/table/theme-rtl.css 152 B
build/block-library/blocks/table/theme.css 152 B
build/block-library/blocks/tag-cloud/editor-rtl.css 92 B
build/block-library/blocks/tag-cloud/editor.css 92 B
build/block-library/blocks/tag-cloud/style-rtl.css 248 B
build/block-library/blocks/tag-cloud/style.css 248 B
build/block-library/blocks/template-part/editor-rtl.css 368 B
build/block-library/blocks/template-part/editor.css 368 B
build/block-library/blocks/template-part/theme-rtl.css 113 B
build/block-library/blocks/template-part/theme.css 113 B
build/block-library/blocks/term-description/style-rtl.css 126 B
build/block-library/blocks/term-description/style.css 126 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 165 B
build/block-library/blocks/text-columns/style.css 165 B
build/block-library/blocks/verse/style-rtl.css 98 B
build/block-library/blocks/verse/style.css 98 B
build/block-library/blocks/video/editor-rtl.css 413 B
build/block-library/blocks/video/editor.css 414 B
build/block-library/blocks/video/style-rtl.css 202 B
build/block-library/blocks/video/style.css 202 B
build/block-library/blocks/video/theme-rtl.css 134 B
build/block-library/blocks/video/theme.css 134 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.08 kB
build/block-library/common.css 1.08 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 11.4 kB
build/block-library/editor.css 11.4 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 230 kB
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 15 kB
build/block-library/style.css 15 kB
build/block-library/theme-rtl.css 715 B
build/block-library/theme.css 719 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 52.6 kB
build/commands/index.min.js 16.2 kB
build/commands/style-rtl.css 956 B
build/commands/style.css 953 B
build/components/index.min.js 249 kB
build/components/style-rtl.css 13.6 kB
build/components/style.css 13.6 kB
build/compose/index.min.js 12.8 kB
build/core-commands/index.min.js 3.09 kB
build/core-data/index.min.js 74.9 kB
build/customize-widgets/index.min.js 11 kB
build/customize-widgets/style-rtl.css 1.43 kB
build/customize-widgets/style.css 1.43 kB
build/data-controls/index.min.js 641 B
build/data/index.min.js 8.67 kB
build/date/index.min.js 18 kB
build/deprecated/index.min.js 458 B
build/dom-ready/index.min.js 325 B
build/dom/index.min.js 4.68 kB
build/edit-post/classic-rtl.css 577 B
build/edit-post/classic.css 578 B
build/edit-post/index.min.js 13.4 kB
build/edit-post/style-rtl.css 2.69 kB
build/edit-post/style.css 2.69 kB
build/edit-site/index.min.js 234 kB
build/edit-site/posts-rtl.css 8.68 kB
build/edit-site/posts.css 8.69 kB
build/edit-site/style-rtl.css 14.7 kB
build/edit-site/style.css 14.7 kB
build/edit-widgets/index.min.js 17.7 kB
build/edit-widgets/style-rtl.css 4.05 kB
build/edit-widgets/style.css 4.06 kB
build/editor/index.min.js 126 kB
build/editor/style-rtl.css 9.2 kB
build/editor/style.css 9.2 kB
build/element/index.min.js 4.82 kB
build/escape-html/index.min.js 537 B
build/format-library/style-rtl.css 472 B
build/format-library/style.css 472 B
build/hooks/index.min.js 1.65 kB
build/html-entities/index.min.js 467 B
build/i18n/index.min.js 2.23 kB
build/is-shallow-equal/index.min.js 526 B
build/keyboard-shortcuts/index.min.js 1.31 kB
build/keycodes/index.min.js 1.46 kB
build/list-reusable-blocks/index.min.js 2.13 kB
build/list-reusable-blocks/style-rtl.css 847 B
build/list-reusable-blocks/style.css 848 B
build/media-utils/index.min.js 3.69 kB
build/notices/index.min.js 946 B
build/nux/index.min.js 1.62 kB
build/nux/style-rtl.css 767 B
build/nux/style.css 763 B
build/patterns/index.min.js 7.36 kB
build/patterns/style-rtl.css 687 B
build/patterns/style.css 685 B
build/plugins/index.min.js 1.86 kB
build/preferences-persistence/index.min.js 2.06 kB
build/preferences/index.min.js 2.9 kB
build/preferences/style-rtl.css 562 B
build/preferences/style.css 562 B
build/primitives/index.min.js 829 B
build/priority-queue/index.min.js 1.54 kB
build/private-apis/index.min.js 978 B
build/react-i18n/index.min.js 630 B
build/react-refresh-entry/index.min.js 9.47 kB
build/react-refresh-runtime/index.min.js 6.76 kB
build/redux-routine/index.min.js 2.7 kB
build/reusable-blocks/index.min.js 2.53 kB
build/reusable-blocks/style-rtl.css 255 B
build/reusable-blocks/style.css 255 B
build/rich-text/index.min.js 12.2 kB
build/router/index.min.js 5.44 kB
build/server-side-render/index.min.js 1.6 kB
build/shortcode/index.min.js 1.4 kB
build/style-engine/index.min.js 2.04 kB
build/token-list/index.min.js 581 B
build/url/index.min.js 3.97 kB
build/vendors/react-dom.min.js 41.7 kB
build/vendors/react-jsx-runtime.min.js 556 B
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 965 B
build/vips/index.min.js 36.2 kB
build/warning/index.min.js 250 B
build/widgets/index.min.js 7.16 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.04 kB

compressed-size-action

@Mamaduka
Copy link
Member

Mamaduka commented Aug 5, 2025

The content-only editing can be used without the write mode. Do we want this change to affect blocks inside containers when with templateLock: 'contentOnly'? @jeryj and I discovered the same problem in the recent PR - #70946.

Hardcoding "essential" formats seems limiting for extenders. What if they provide custom formats that are essential for their editorial teams?

Maybe we should consider letting formats declare themself as essential for write mode.

Screenshot

CleanShot 2025-08-05 at 14 01 01

@getdave
Copy link
Contributor Author

getdave commented Aug 5, 2025

@Mamaduka Thanks for flagging. It's been a while.

So is Write Mode equivalent to isNavigationMode && blockEditingMode === 'contentOnly'?

@Mamaduka
Copy link
Member

Mamaduka commented Aug 5, 2025

Indeed, it's been a while.

So is Write Mode equivalent to isNavigationMode && blockEditingMode === 'contentOnly'?

As far as I know, that's the way to derive writing mode state; the feature is still behind the experimental flag.

@getdave
Copy link
Contributor Author

getdave commented Aug 5, 2025

Maybe we should consider letting formats declare themself as essential for write mode.

Yes. Let me explore that approach.

@jeryj
Copy link
Contributor

jeryj commented Aug 5, 2025

Maybe we should consider letting formats declare themself as essential for write mode.

I agree with this in principle. I do have concerns over it being abused. If I'm a plugin author trying to promote my tooling, why would I not define it as essential? It will promote my product and also save me questions about why they can't find the tool if they're in Write mode and don't understand Write vs Design.

At the same time, I agree we should allow this to be extended. I'm not sure how to approach it, but I'd like to do it in a way that we can explore moving forwards without being locked into a decision if possible :)

Hardcoding "essential" formats seems limiting for extenders. What if they provide custom formats that are essential for their editorial teams?

Maybe I expanded the conversation too far beyond just formats. I'm trying to think from a high level how to let tools opt-in to showing in write mode. At the moment, it seems like it has to be done within the JS which feels really ad-hoc. This may be a terrible idea - but could we add a flag to some base-level components like ToolbarItem, ToolbarButton, etc for isContentEssential={ false } or similar that forces an opt-in behavior to display your toolbar item in write mode?

@Mamaduka
Copy link
Member

Mamaduka commented Aug 5, 2025

I agree with this in principle. I do have concerns over it being abused. If I'm a plugin author trying to promote my tooling, why would I not define it as essential?

IMO, all public APIs can be abused; the best we can do here is to tuck them in a dropdown (similar to the current system).

P.S. It is still possible for custom format libraries to render items in the write mode toolbar if they know how SlotFills work.

getdave added 5 commits August 6, 2025 17:02
- Add withWriteModeFilter HOC to filter format edit components in write mode
- Add essential property to format registrations (default: false)
- Mark core formats (bold, italic, link, strikethrough) as essential
- Apply HOC in FormatEdit component to filter non-essential formats
- Maintain backward compatibility for existing formats
- Enable third-party plugins to opt formats into write mode via essential flag

This allows write mode to show only essential formatting controls while
maintaining full functionality in default mode.
const isWriteMode = isNavigationMode && isContentOnlyMode;

// In write mode, only show essential formats
if ( isWriteMode && ! formatSettings?.essential ) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that if essential is not defined then the default behaviour is not to show in Write Mode.

getdave added 2 commits August 6, 2025 18:01
- Add early return for Write Mode experiment flag check
- Avoid applying HOC when experiment is not enabled
- Maintain existing optimizations for essential formats
- Improve performance by reducing unnecessary wrapper components
@getdave getdave marked this pull request as draft August 6, 2025 17:17
@getdave getdave marked this pull request as ready for review August 6, 2025 17:21
@getdave getdave requested a review from Mamaduka August 6, 2025 17:23
@getdave getdave requested a review from jeryj August 6, 2025 17:23
@getdave
Copy link
Contributor Author

getdave commented Aug 6, 2025

Maybe we should consider letting formats declare themself as essential for write mode.

@Mamaduka @jeryj I went ahead and implemented one route for this. I didn't want to remove formats entirely or stop their registration because that's not the goal of Write Mode. All we want to do is not provide the UI in write mode.

I found the best way to do that was to wrap the format's Edit definition with a HoC that will only render in Write Mode if the format is registered with __unstableEssential (feel free to suggest alternative names).

I provided early escape hatches and also ensured it doesn't filter the component if the experiment isn't active.

I updated the description and you can follow the testing instructions there to get a feel for how this works.

The result is the same as per the original implementation, but just with the benefit that extenders can choose to mark their formats as essential to force them to show in Write Mode (under the dropdown).

@jeryj
Copy link
Contributor

jeryj commented Aug 6, 2025

What if packages/block-editor/src/components/rich-text/with-write-mode-filter.js or similar was utilized within the <BlockToolbar /> component instead? Then any Block Toolbar button or item could add the _unstableEssential (or whatever we end up calling it).

@Mamaduka
Copy link
Member

Mamaduka commented Aug 7, 2025

@getdave, what do you think about handling all related filtering inside the useFormatTypes? It already has similar logic for interactive formats. A small bonus point is that it won't create a store subscription for each format when a block is selected.

@jeryj, that might be more on a higher level, but it would be hard to control what BlockToolbar renders, since slots usually don't have that info.

Example patch, still requires full removal of `withWriteModeFilter`

diff --git a/packages/block-editor/src/components/rich-text/format-edit.js b/packages/block-editor/src/components/rich-text/format-edit.js
index 20d13c1d3f..a70b9f8f77 100644
--- a/packages/block-editor/src/components/rich-text/format-edit.js
+++ b/packages/block-editor/src/components/rich-text/format-edit.js
@@ -8,7 +8,6 @@ import { useContext, useMemo } from '@wordpress/element';
  * Internal dependencies
  */
 import BlockContext from '../block-context';
-import withWriteModeFilter from './with-write-mode-filter';
 
 const DEFAULT_BLOCK_CONTEXT = {};
 
@@ -38,9 +37,6 @@ function Edit( { onChange, onFocus, value, forwardedRef, settings } ) {
 		return null;
 	}
 
-	// Apply the write mode filter HOC
-	const FilteredEditFunction = withWriteModeFilter( EditFunction, settings );
-
 	const activeFormat = getActiveFormat( value, name );
 	const isActive = activeFormat !== undefined;
 	const activeObject = getActiveObject( value );
@@ -48,7 +44,7 @@ function Edit( { onChange, onFocus, value, forwardedRef, settings } ) {
 		activeObject !== undefined && activeObject.type === name;
 
 	return (
-		<FilteredEditFunction
+		<EditFunction
 			key={ name }
 			isActive={ isActive }
 			activeAttributes={ isActive ? activeFormat.attributes || {} : {} }
diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js
index 768ffbb0cd..bd831558f7 100644
--- a/packages/block-editor/src/components/rich-text/index.js
+++ b/packages/block-editor/src/components/rich-text/index.js
@@ -132,8 +132,12 @@ export function RichTextWrapper(
 			return { isSelected: false };
 		}
 
-		const { getSelectionStart, getSelectionEnd } =
-			select( blockEditorStore );
+		const {
+			getSelectionStart,
+			getSelectionEnd,
+			getBlockEditingMode,
+			isNavigationMode,
+		} = select( blockEditorStore );
 		const selectionStart = getSelectionStart();
 		const selectionEnd = getSelectionEnd();
 
@@ -154,15 +158,21 @@ export function RichTextWrapper(
 			selectionStart: isSelected ? selectionStart.offset : undefined,
 			selectionEnd: isSelected ? selectionEnd.offset : undefined,
 			isSelected,
+			isWriteMode:
+				isNavigationMode() &&
+				getBlockEditingMode( clientId ) === 'contentOnly',
 		};
 	};
-	const { selectionStart, selectionEnd, isSelected } = useSelect( selector, [
-		clientId,
-		identifier,
-		instanceId,
-		originalIsSelected,
-		isBlockSelected,
-	] );
+	const { selectionStart, selectionEnd, isSelected, isWriteMode } = useSelect(
+		selector,
+		[
+			clientId,
+			identifier,
+			instanceId,
+			originalIsSelected,
+			isBlockSelected,
+		]
+	);
 
 	const { disableBoundBlock, bindingsPlaceholder, bindingsLabel } = useSelect(
 		( select ) => {
@@ -323,8 +333,9 @@ export function RichTextWrapper(
 	} = useFormatTypes( {
 		clientId,
 		identifier,
-		withoutInteractiveFormatting,
 		allowedFormats: adjustedAllowedFormats,
+		withoutInteractiveFormatting,
+		disableNoneEssentialFormatting: isWriteMode,
 	} );
 
 	function addEditorOnlyFormats( value ) {
diff --git a/packages/block-editor/src/components/rich-text/use-format-types.js b/packages/block-editor/src/components/rich-text/use-format-types.js
index 0bbebbf262..fcb58a852c 100644
--- a/packages/block-editor/src/components/rich-text/use-format-types.js
+++ b/packages/block-editor/src/components/rich-text/use-format-types.js
@@ -56,35 +56,48 @@ function getPrefixedSelectKeys( selected, prefix ) {
  * This hook provides RichText with the `formatTypes` and its derived props from
  * experimental format type settings.
  *
- * @param {Object}  $0                              Options
- * @param {string}  $0.clientId                     Block client ID.
- * @param {string}  $0.identifier                   Block attribute.
- * @param {boolean} $0.withoutInteractiveFormatting Whether to clean the interactive formatting or not.
- * @param {Array}   $0.allowedFormats               Allowed formats
+ * @param {Object}  options                                Options
+ * @param {string}  options.clientId                       Block client ID.
+ * @param {string}  options.identifier                     Block attribute.
+ * @param {Array}   options.allowedFormats                 Allowed formats
+ * @param {boolean} options.withoutInteractiveFormatting   Whether to clean the interactive formatting or not.
+ * @param {boolean} options.disableNoneEssentialFormatting Whether to disable none-essential formatting or not.
  */
 export function useFormatTypes( {
 	clientId,
 	identifier,
-	withoutInteractiveFormatting,
 	allowedFormats,
+	withoutInteractiveFormatting,
+	disableNoneEssentialFormatting = false,
 } ) {
 	const allFormatTypes = useSelect( formatTypesSelector, [] );
 	const formatTypes = useMemo( () => {
-		return allFormatTypes.filter( ( { name, interactive, tagName } ) => {
-			if ( allowedFormats && ! allowedFormats.includes( name ) ) {
-				return false;
-			}
+		return allFormatTypes.filter(
+			( { name, interactive, tagName, __unstableEssential } ) => {
+				if ( allowedFormats && ! allowedFormats.includes( name ) ) {
+					return false;
+				}
 
-			if (
-				withoutInteractiveFormatting &&
-				( interactive || interactiveContentTags.has( tagName ) )
-			) {
-				return false;
-			}
+				if ( disableNoneEssentialFormatting && ! __unstableEssential ) {
+					return false;
+				}
+
+				if (
+					withoutInteractiveFormatting &&
+					( interactive || interactiveContentTags.has( tagName ) )
+				) {
+					return false;
+				}
 
-			return true;
-		} );
-	}, [ allFormatTypes, allowedFormats, withoutInteractiveFormatting ] );
+				return true;
+			}
+		);
+	}, [
+		allFormatTypes,
+		allowedFormats,
+		disableNoneEssentialFormatting,
+		withoutInteractiveFormatting,
+	] );
 	const keyedSelected = useSelect(
 		( select ) =>
 			formatTypes.reduce( ( accumulator, type ) => {

- Remove withWriteModeFilter HOC approach in favor of centralized filtering
- Add Write Mode detection to RichText component with experiment flag check
- Enhance useFormatTypes hook with disableNoneEssentialFormatting parameter
- Centralize all format filtering logic in one place for better performance
- Eliminate individual store subscriptions per format component
- Maintain same functionality with improved architecture and performance
@getdave
Copy link
Contributor Author

getdave commented Aug 7, 2025

...what do you think about handling all related filtering inside the useFormatTypes?

I looked at this yesterday and dismissed it, but looking again it seems ideal. I'll make some adjustments.

@getdave getdave marked this pull request as draft August 7, 2025 10:11
getdave added 3 commits August 7, 2025 11:12
- isNavigationMode() already checks window.__experimentalEditorWriteMode internally
- Removes duplicate experiment flag check for cleaner, more efficient code
- Maintains same functionality while following existing codebase patterns
- Add essentialFormatKey to private-keys.js for internal Core usage
- Expose essentialFormatKey in private-apis.js for Core packages
- Update use-format-types.js to use private Symbol instead of __unstableEssential
- Update format library packages (bold, italic, link) to use private Symbol
- Follow correct import pattern: unlock from lock-unlock.js + privateApis from block-editor
- Remove unstable API prefix as per Core guidelines
- More accurately reflects that we're checking both Write Mode and content-only editing
- Makes the dual condition check explicit: Write Mode + content-only block editing
- Improves code readability and maintainability
- Clarifies the relationship between Write Mode and content editing mode
@getdave getdave marked this pull request as ready for review August 7, 2025 10:36
@getdave getdave added the Needs Dev Note Requires a developer note for a major WordPress release cycle label Aug 7, 2025
@getdave getdave requested a review from Mamaduka August 7, 2025 10:39
Copy link
Member

@Mamaduka Mamaduka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the follow-ups, @getdave!

The changes work as advertised.

originalIsSelected,
isBlockSelected,
] );
const { selectionStart, selectionEnd, isSelected, isContentOnlyWriteMode } =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you didn't introduce it in this PR, just wondering if there's a reason why selector is passed as a const here rather than just doing it inline like we usually do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure. Seems odd.

Copy link
Contributor

@scruffian scruffian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great, thank you!

(I had to add privateApis as blockEditorPrivateApis, to the @wordpress/block-editor import, to get strikethrough to show up)

@getdave getdave merged commit 5f5bce3 into trunk Aug 8, 2025
84 checks passed
@getdave getdave deleted the feature/restrict-formatting-controls-in-write-mode branch August 8, 2025 10:02
@github-actions github-actions bot added this to the Gutenberg 21.5 milestone Aug 8, 2025
@getdave
Copy link
Contributor Author

getdave commented Aug 8, 2025

WP 6.9 note - Stabilization Needed

The essential property should be stabilized as a public API to allow plugin developers to create formats that are visible in Write Mode.

This would enable plugins to provide essential formatting options that integrate seamlessly with the Write Mode experience.

Note that this PR implements the feature as private via a Symbol.

@getdave
Copy link
Contributor Author

getdave commented Aug 8, 2025

@Mamaduka @scruffian @jeryj @talldan I've been thinking about PRs like this one which alter the UI based on a combo of

  • "is write mode"
  • "is block contentOnly"

I'm thinking that actually we don't need the "is write mode" check at all as blocks marked as contentOnly shiould have consistent behaiour regardless of the wider editor mode.

To me "write mode" should just dictate the state of blocks (e.g. contentOnly, disabled...etc). The way those blocks actually behave (e.g. restricted UI...etc) in those states is up to them.

I'd value your thoughts on:

  • is my thinking correct
  • are their any edge cases I haven't considered

If I'm correct then I'll raise PRs to update the recent implementations I've made.

@talldan
Copy link
Contributor

talldan commented Aug 8, 2025

I'm thinking that actually we don't need the "is write mode" check at all as blocks marked as contentOnly shiould have consistent behaiour regardless of the wider editor mode.

You might need to check whether a block is a section as well, as I think they might also be content only. Not 100% sure, just trying to remember.

@scruffian scruffian restored the feature/restrict-formatting-controls-in-write-mode branch August 8, 2025 14:57
@Mamaduka Mamaduka deleted the feature/restrict-formatting-controls-in-write-mode branch August 10, 2025 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Write mode Needs Dev Note Requires a developer note for a major WordPress release cycle [Package] Format library /packages/format-library [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants