diff --git a/changelog.txt b/changelog.txt
index 193fe8dfe70f68..85fa8cc791aec4 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,434 @@
== Changelog ==
+= 17.6.0-rc.1 =
+
+
+## Changelog
+
+### Features
+
+#### Interactivity API
+- Add `wp-data-on-window` and `wp-data-on-document` directives. ([57931](https://github.com/WordPress/gutenberg/pull/57931))
+- Add `wp-each` directive. ([57859](https://github.com/WordPress/gutenberg/pull/57859))
+- Add `wp-run` directive and `useInit` & `useWatch` hooks. ([57805](https://github.com/WordPress/gutenberg/pull/57805))
+
+#### Typography
+- Add defaultFontSizes option to theme.json. ([56661](https://github.com/WordPress/gutenberg/pull/56661))
+- Font Library: Add wp_get_font_dir() function. ([57730](https://github.com/WordPress/gutenberg/pull/57730))
+
+#### Custom Fields
+- Block Bindings: Disable editing of bound block attributes in editor UI. ([58085](https://github.com/WordPress/gutenberg/pull/58085))
+
+#### Block Editor
+- Add effects/box shadow tools to block inspector. ([57654](https://github.com/WordPress/gutenberg/pull/57654))
+
+
+### Enhancements
+
+- Add gettext content when translating 'Header'. ([51066](https://github.com/WordPress/gutenberg/pull/51066))
+- Disable lock button if user cannot control lock state. ([57274](https://github.com/WordPress/gutenberg/pull/57274))
+- Editor: Unify the Editor Mode preference. ([57642](https://github.com/WordPress/gutenberg/pull/57642))
+- Element: Start reexporting PureComponent. ([58076](https://github.com/WordPress/gutenberg/pull/58076))
+- Live Preview: Show the current theme name on the theme activation modal. ([57588](https://github.com/WordPress/gutenberg/pull/57588))
+- Remove right negative margin from pinned items. ([57666](https://github.com/WordPress/gutenberg/pull/57666))
+- Unify the preferences modal UI between post and site editor. ([57639](https://github.com/WordPress/gutenberg/pull/57639))
+- Update style revision top toolbar text. ([58057](https://github.com/WordPress/gutenberg/pull/58057))
+- Use ClipboardJS latest version and clean up focus loss workaround. ([57156](https://github.com/WordPress/gutenberg/pull/57156))
+
+#### Components
+- Add opt-in prop for 40px default size for `BoxControl`, `BorderControl`, and `BorderBoxControl`. ([56185](https://github.com/WordPress/gutenberg/pull/56185))
+- BorderControl: Replace style picker with ToggleGroupControl. ([57562](https://github.com/WordPress/gutenberg/pull/57562))
+- ColorPicker: Store internal HSLA state for better slider UX. ([57555](https://github.com/WordPress/gutenberg/pull/57555))
+- Migrate PaletteEdit and CircularOptionPicker tests from user-event to ariakit/test. ([57809](https://github.com/WordPress/gutenberg/pull/57809))
+- Replace `TabPanel` with `Tabs` in the Editor Preferences Modal. ([57293](https://github.com/WordPress/gutenberg/pull/57293))
+- Theme: Set `color` on wrapper. ([58095](https://github.com/WordPress/gutenberg/pull/58095))
+- Tooltip: No-op when nested inside another Tooltip component. ([57202](https://github.com/WordPress/gutenberg/pull/57202))
+- `BoxControl`: Update design. ([56665](https://github.com/WordPress/gutenberg/pull/56665))
+
+#### Interactivity API
+- Render the root interactive blocks. ([57729](https://github.com/WordPress/gutenberg/pull/57729))
+- Interactivity Router: Replace `data-wp-navigation-id` with `data-wp-router-region`. ([58191](https://github.com/WordPress/gutenberg/pull/58191))
+- Interactivity: Export `withScope()` and allow to use it with asynchronous operations. ([58013](https://github.com/WordPress/gutenberg/pull/58013))
+- Prevent the use of components in `wp-text`. ([57879](https://github.com/WordPress/gutenberg/pull/57879))
+- Remove wp-data-navigation-link directive. ([57853](https://github.com/WordPress/gutenberg/pull/57853))
+- Server Directive Processing Refactor. ([58066](https://github.com/WordPress/gutenberg/pull/58066))
+- Update `preact`, `@preact/signals` and `deepsignal` dependencies. ([57891](https://github.com/WordPress/gutenberg/pull/57891))
+
+#### Block Editor
+- Add copy link button to Link UI. ([58170](https://github.com/WordPress/gutenberg/pull/58170))
+- Improve LinkControl preview. ([57775](https://github.com/WordPress/gutenberg/pull/57775))
+- Keep Link UI open upon initial link creation when used in RichText. ([57726](https://github.com/WordPress/gutenberg/pull/57726))
+- List View: Displace list view items when dragging (a bit more WYSIWYG). ([56625](https://github.com/WordPress/gutenberg/pull/56625))
+- Show initial suggestions in rich text Link UI. ([57743](https://github.com/WordPress/gutenberg/pull/57743))
+
+#### Typography
+- Font Library Modal: Reset the selected font when installing a new font. ([57817](https://github.com/WordPress/gutenberg/pull/57817))
+- Font Library: Disable font library UI using a PHP filter. ([57818](https://github.com/WordPress/gutenberg/pull/57818))
+- Font Library: Filter fonts upload directory. ([57697](https://github.com/WordPress/gutenberg/pull/57697))
+- Font Library: Use data or src file to define font collection data. ([57734](https://github.com/WordPress/gutenberg/pull/57734))
+
+#### Block Library
+- Add more taxonomy options to the post navigation link. ([48912](https://github.com/WordPress/gutenberg/pull/48912))
+- Add: Footnotes support for other CPT's. ([57353](https://github.com/WordPress/gutenberg/pull/57353))
+- Better navigation link variations for post types / taxonomies. ([56100](https://github.com/WordPress/gutenberg/pull/56100))
+- Remove "blocks" from copy and delete labels. ([57769](https://github.com/WordPress/gutenberg/pull/57769))
+
+#### Data Views
+- DataViews: Enable grid layout for templates & parts by default. ([58137](https://github.com/WordPress/gutenberg/pull/58137))
+- DataViews: Make dataviews powered page patterns stable. ([58139](https://github.com/WordPress/gutenberg/pull/58139))
+- DataViews: Make the "Manage Pages" stable. ([58166](https://github.com/WordPress/gutenberg/pull/58166))
+
+#### Site Editor
+- Group templates in sidebar list. ([57711](https://github.com/WordPress/gutenberg/pull/57711))
+- Initial routing refactoring to separate preview from list view. ([57938](https://github.com/WordPress/gutenberg/pull/57938))
+- Iterate on warning text for block removal for query/post template/post content. ([58138](https://github.com/WordPress/gutenberg/pull/58138))
+
+#### Block API
+- Block Bindings: Update source registration syntax and remove APIs that should be private. ([58205](https://github.com/WordPress/gutenberg/pull/58205))
+- Block Hooks: Do not remove toggle if hooked block is present elsewhere. ([57928](https://github.com/WordPress/gutenberg/pull/57928))
+
+#### Synced Patterns
+- Add basic pattern overrides end-to-end tests. ([57792](https://github.com/WordPress/gutenberg/pull/57792))
+- Use a patch format and support `linkTarget` of `core/button` for Pattern Overrides. ([58165](https://github.com/WordPress/gutenberg/pull/58165))
+
+#### Patterns
+- Add image block support for pattern overrides. ([57909](https://github.com/WordPress/gutenberg/pull/57909))
+- Outline editable blocks that are within a content-locked container. ([57901](https://github.com/WordPress/gutenberg/pull/57901))
+
+#### Post Editor
+- Post Lock: Use the new modal size preset. ([58197](https://github.com/WordPress/gutenberg/pull/58197))
+
+#### Font Library
+- Update the default collection data URL to the wordpress.org cdn. ([58186](https://github.com/WordPress/gutenberg/pull/58186))
+
+#### Commands
+- Minor command tweaks. ([58148](https://github.com/WordPress/gutenberg/pull/58148))
+
+#### Extensibility
+- Update Navigation block to render hooked inner blocks. ([57754](https://github.com/WordPress/gutenberg/pull/57754))
+
+#### Global Styles
+- Site editor: Add global styles changes to save flow. ([57470](https://github.com/WordPress/gutenberg/pull/57470))
+
+#### Design Tools
+- Pullquote Block: Add padding and margin support. ([45731](https://github.com/WordPress/gutenberg/pull/45731))
+
+
+### New APIs
+
+#### Block API
+- Block Bindings API: Add block bindings PHP registration mechanisms and "Post meta" source under the experimental flag. ([57249](https://github.com/WordPress/gutenberg/pull/57249))
+- Block Bindings API: Refactor logic into Block Bindings class and singleton pattern. ([57742](https://github.com/WordPress/gutenberg/pull/57742))
+
+
+### Bug Fixes
+
+- (editor)(fix) Append the `edit-post-header-toolbar` class in NavigableToolbar for backward compatibility with plugin GUI injections. ([58154](https://github.com/WordPress/gutenberg/pull/58154))
+- Bring back the chevron. ([57807](https://github.com/WordPress/gutenberg/pull/57807))
+- Fix flaky "create a new pattern" test. ([57747](https://github.com/WordPress/gutenberg/pull/57747))
+- Fix site editor layout regressions. ([58077](https://github.com/WordPress/gutenberg/pull/58077))
+- Preferences: Add a proxy to retrieve the deprecated preferences with a deprecation message. ([58016](https://github.com/WordPress/gutenberg/pull/58016))
+- Remove unused argument from sprintf in pagination.js. ([57823](https://github.com/WordPress/gutenberg/pull/57823))
+- core-js: Only polyfill stable features. ([57674](https://github.com/WordPress/gutenberg/pull/57674))
+
+#### Block Library
+- Avatar block: Fix broken aligments in the editor. ([58114](https://github.com/WordPress/gutenberg/pull/58114))
+- Embed Block: Fix retry processing when embedding with trailing slash fails. ([58007](https://github.com/WordPress/gutenberg/pull/58007))
+- Lightbox: Fix "Expand on click" control being disabled unintentionally. ([56053](https://github.com/WordPress/gutenberg/pull/56053))
+- Modified Date Block: Don't render change date tool. ([57914](https://github.com/WordPress/gutenberg/pull/57914))
+- Only prioritise Quote transform where relevant. ([57749](https://github.com/WordPress/gutenberg/pull/57749))
+- Query Loop: Fix posts list variation detection. ([58194](https://github.com/WordPress/gutenberg/pull/58194))
+
+#### Components
+- Button: Always render the Tooltip component even when a tooltip should not be shown. ([56490](https://github.com/WordPress/gutenberg/pull/56490))
+- CustomSelect: Adjust `renderSelectedValue` to fix sizing. ([57865](https://github.com/WordPress/gutenberg/pull/57865))
+- ToggleGroupControl: Improve controlled value detection. ([57770](https://github.com/WordPress/gutenberg/pull/57770))
+- Tooltip: Accept specific tooltip props. ([58125](https://github.com/WordPress/gutenberg/pull/58125))
+- Tooltip: Forward and merge inner tooltip props correctly. ([57878](https://github.com/WordPress/gutenberg/pull/57878))
+
+#### Data Views
+- DataViews: Default sort order in templates by title. ([58175](https://github.com/WordPress/gutenberg/pull/58175))
+- DataViews: Don't always display horizontal scrollbar. ([58101](https://github.com/WordPress/gutenberg/pull/58101))
+- DataViews: Fix author sorting in templates and template parts. ([58167](https://github.com/WordPress/gutenberg/pull/58167))
+
+#### Patterns
+- Add black border back when editing synced pattern in the post editor. ([57631](https://github.com/WordPress/gutenberg/pull/57631))
+- Outline editable blocks when in a pattern that has locked children. ([57991](https://github.com/WordPress/gutenberg/pull/57991))
+- Remove text align control for paragraph, heading, and button in contentOnly editing mode. ([57906](https://github.com/WordPress/gutenberg/pull/57906))
+
+#### List View
+- Image Block: Make block name affect list view. ([57955](https://github.com/WordPress/gutenberg/pull/57955))
+- More Block: Make block name affect list view. ([58160](https://github.com/WordPress/gutenberg/pull/58160))
+
+#### Block API
+- Block Hooks: Fix toggle. ([57956](https://github.com/WordPress/gutenberg/pull/57956))
+- Fix formats not working in block bindings content. ([58055](https://github.com/WordPress/gutenberg/pull/58055))
+
+#### Global Styles
+- Correctly decode border color values. ([57876](https://github.com/WordPress/gutenberg/pull/57876))
+- Fix: Theme.json application of custom root selector for styles. ([58050](https://github.com/WordPress/gutenberg/pull/58050))
+
+#### Data Layer
+- Data: Allow binding registry selector to multiple registries. ([57943](https://github.com/WordPress/gutenberg/pull/57943))
+- Data: Fix memoized createRegistrySelector. ([57888](https://github.com/WordPress/gutenberg/pull/57888))
+
+#### Typography
+- #56734 When there is no font, the border should not appear. Display further guidance text. ([56825](https://github.com/WordPress/gutenberg/pull/56825))
+- Fluid typography: Do not calculate fluid font size when min and max viewport widths are equal. ([57866](https://github.com/WordPress/gutenberg/pull/57866))
+
+#### Block Editor
+- Fix regression: Content locking does not stops when an outside block is selected. ([57737](https://github.com/WordPress/gutenberg/pull/57737))
+- LinkControl: Remove unnecessary right padding of input fields. ([57784](https://github.com/WordPress/gutenberg/pull/57784))
+
+#### Custom Fields
+- Block Bindings: Fix button popover not showing in patterns. ([58219](https://github.com/WordPress/gutenberg/pull/58219))
+
+#### Font Library
+- Fix typo. ([58193](https://github.com/WordPress/gutenberg/pull/58193))
+
+#### Synced Patterns
+- Fix losing overrides after detaching patterns. ([58164](https://github.com/WordPress/gutenberg/pull/58164))
+
+#### Interactivity API
+- Prevent `wp-data-on=""` from creating `onDefault` handlers. ([57925](https://github.com/WordPress/gutenberg/pull/57925))
+
+#### CSS & Styling
+- Styles revisions: Remove body padding. ([57748](https://github.com/WordPress/gutenberg/pull/57748))
+
+#### Templates API
+- Fix visual indication of switch to default template in the post editor. ([57718](https://github.com/WordPress/gutenberg/pull/57718))
+
+
+### Accessibility
+
+#### Site Editor
+- Fix font variants count color contrast ratio and l10n. ([58117](https://github.com/WordPress/gutenberg/pull/58117))
+- Make the site hub View Site link always visible. ([57423](https://github.com/WordPress/gutenberg/pull/57423))
+
+#### Block Editor
+- Fix parent selector button focus style and metrics. ([57728](https://github.com/WordPress/gutenberg/pull/57728))
+- Restore visual separator between mover buttons when show button label is on. ([57640](https://github.com/WordPress/gutenberg/pull/57640))
+
+#### Widgets Editor
+- Fix Widgets page Undo and Redo accessibility and keyboard interaction. ([57677](https://github.com/WordPress/gutenberg/pull/57677))
+
+
+### Performance
+
+- Add patterns load test. ([57828](https://github.com/WordPress/gutenberg/pull/57828))
+- Block editor: Avoid list re-rendering on select. ([57188](https://github.com/WordPress/gutenberg/pull/57188))
+- Block editor: Don't register shortcuts for preview editors. ([57984](https://github.com/WordPress/gutenberg/pull/57984))
+- Block editor: Fix performance regression after #57950. ([57971](https://github.com/WordPress/gutenberg/pull/57971))
+- Block editor: Use context for useBlockEditingMode. ([57950](https://github.com/WordPress/gutenberg/pull/57950))
+- BlockSwitcher: Defer transform calculations until the Dropdown is open. ([57892](https://github.com/WordPress/gutenberg/pull/57892))
+- Call variation through callback so it's only loaded when needed - in support of trac 59969. ([56952](https://github.com/WordPress/gutenberg/pull/56952))
+- Editor styles: Cache transform. ([57810](https://github.com/WordPress/gutenberg/pull/57810))
+- Footnotes: Combine format store subscription. ([58129](https://github.com/WordPress/gutenberg/pull/58129))
+- Iframe: Calc compat styles once per page load. ([57798](https://github.com/WordPress/gutenberg/pull/57798))
+- Measure typing with the top toolbar enabled. ([57709](https://github.com/WordPress/gutenberg/pull/57709))
+- Meta boxes: Don't initialise if there are none. ([57182](https://github.com/WordPress/gutenberg/pull/57182))
+- Patterns: Avoid fetching on load. ([57999](https://github.com/WordPress/gutenberg/pull/57999))
+- Site editor: Avoid fetching themes on load. ([57985](https://github.com/WordPress/gutenberg/pull/57985))
+- Site editor: Reduce artificial loading delay from 1s to 100ms. ([57953](https://github.com/WordPress/gutenberg/pull/57953))
+- Site editor: Remove store subscription per block. ([57995](https://github.com/WordPress/gutenberg/pull/57995))
+- Template Part & Query: Avoid server requests on mount. ([57987](https://github.com/WordPress/gutenberg/pull/57987))
+- Template part block: Avoid parsing ALL patterns on mount. ([57856](https://github.com/WordPress/gutenberg/pull/57856))
+
+#### Block Editor
+- Revert "Block editor: Avoid list re-rendering on select". ([58147](https://github.com/WordPress/gutenberg/pull/58147))
+
+#### Post Editor
+- Editor: Use hooks instead of HoCs for `EditorNotices`. ([57772](https://github.com/WordPress/gutenberg/pull/57772))
+
+
+### Experiments
+
+#### Data Views
+- Add: Bulk actions to dataviews with the new design. ([57255](https://github.com/WordPress/gutenberg/pull/57255))
+- Data view list layout: Fix thumbnail dimensions. ([57774](https://github.com/WordPress/gutenberg/pull/57774))
+- Data views table layout: Update cell vertical alignment. ([57804](https://github.com/WordPress/gutenberg/pull/57804))
+- DataViews: Add description to pages. ([57793](https://github.com/WordPress/gutenberg/pull/57793))
+- DataViews: Add front page to pages page sidebar. ([57759](https://github.com/WordPress/gutenberg/pull/57759))
+- DataViews: Better management of `layout` param in templates. ([58116](https://github.com/WordPress/gutenberg/pull/58116))
+- DataViews: Make list layout the default for templates with the experiment enabled. ([57933](https://github.com/WordPress/gutenberg/pull/57933))
+- DataViews: Revert list view as default for pages. ([58081](https://github.com/WordPress/gutenberg/pull/58081))
+- DataViews: Revert list view as default for templates. ([58079](https://github.com/WordPress/gutenberg/pull/58079))
+- DataViews: Set primary field styles. ([57846](https://github.com/WordPress/gutenberg/pull/57846))
+- DataViews: Show loading / no result message for the list layout. ([57764](https://github.com/WordPress/gutenberg/pull/57764))
+- DataViews: Update template parts view. ([57952](https://github.com/WordPress/gutenberg/pull/57952))
+- DataViews: Use button for patterns, pages and templates preview field. ([58071](https://github.com/WordPress/gutenberg/pull/58071))
+- DataViews: Use table layout for templates when experiment disabled. ([57960](https://github.com/WordPress/gutenberg/pull/57960))
+- Stabilise view options button icon. ([57964](https://github.com/WordPress/gutenberg/pull/57964))
+- Update Grid layout design. ([57880](https://github.com/WordPress/gutenberg/pull/57880))
+- Update Pages preview field display. ([57919](https://github.com/WordPress/gutenberg/pull/57919))
+- Update Templates table layout. ([57930](https://github.com/WordPress/gutenberg/pull/57930))
+- Update: Show template sources on templates Dataviews sidebar. ([58124](https://github.com/WordPress/gutenberg/pull/58124))
+
+#### Synced Patterns
+- Add a control to reset pattern overrides. ([57845](https://github.com/WordPress/gutenberg/pull/57845))
+- Allow heading and button in Pattern Overrides. ([57789](https://github.com/WordPress/gutenberg/pull/57789))
+
+#### Typography
+- Download then upload font face assets when installing from a collection. ([57694](https://github.com/WordPress/gutenberg/pull/57694))
+- Use `slug` instead of `id` for Font Collection. ([57735](https://github.com/WordPress/gutenberg/pull/57735))
+
+#### REST API
+- Font Library Refactor. ([57688](https://github.com/WordPress/gutenberg/pull/57688))
+
+#### Block Editor
+- Allow drag and drop to create Rows and Galleries. ([56186](https://github.com/WordPress/gutenberg/pull/56186))
+
+
+### Documentation
+
+- Add a video demonstration to the Quick Start Guide. ([57834](https://github.com/WordPress/gutenberg/pull/57834))
+- Button: Improve `disabled`-related prop descriptions. ([57864](https://github.com/WordPress/gutenberg/pull/57864))
+- Components: Move CHANGELOG entries under the correct release. ([57885](https://github.com/WordPress/gutenberg/pull/57885))
+- Docs: Fix typo in "The block wrapper" document. ([58106](https://github.com/WordPress/gutenberg/pull/58106))
+- Docs: Use 'key' in 'editor.BlockEdit' filter code examples. ([58119](https://github.com/WordPress/gutenberg/pull/58119))
+- Document files/directories requiring backmerging to WP Core for major release. ([58064](https://github.com/WordPress/gutenberg/pull/58064))
+- Fix the iframe markup of the embed video in the Quick Start Guide. ([57857](https://github.com/WordPress/gutenberg/pull/57857))
+- Fix: Link to the nodejs release page. ([57816](https://github.com/WordPress/gutenberg/pull/57816))
+- Fix: Typo on BlockListBlock comments. ([57814](https://github.com/WordPress/gutenberg/pull/57814))
+- Fix: Typos on __unstableSetTemporarilyEditingAsBlocks documentation. ([57768](https://github.com/WordPress/gutenberg/pull/57768))
+- Font Library: Add font collection JSON schema. ([57736](https://github.com/WordPress/gutenberg/pull/57736))
+- Prefixes all php filters with wpdocs_. ([53914](https://github.com/WordPress/gutenberg/pull/53914))
+- Remove the unnecessary TOC and fix grammar/formatting in the Patterns doc. ([57825](https://github.com/WordPress/gutenberg/pull/57825))
+- Remove the 👋 emoji from the Block Editor Handbook. ([58023](https://github.com/WordPress/gutenberg/pull/58023))
+- Update versions-in-wordpress.md. ([57916](https://github.com/WordPress/gutenberg/pull/57916))
+- [Type] Developer Documentation - Fix removeAllNotices dispatch on the removeAllNotices doc section of @wordpress/notices. ([57436](https://github.com/WordPress/gutenberg/pull/57436))
+
+
+### Code Quality
+
+- Block Renaming - move backported WP 6.5 code to 6.5 compat dir. ([58126](https://github.com/WordPress/gutenberg/pull/58126))
+- Fix comments block. ([57820](https://github.com/WordPress/gutenberg/pull/57820))
+- Fon Library: Remove 'version' property from font collection schema. ([58025](https://github.com/WordPress/gutenberg/pull/58025))
+- Remove unneeded `margin: 0` override for `Notice` component consumer. ([57794](https://github.com/WordPress/gutenberg/pull/57794))
+- Rename __experimentalGetGlobalBlocksByName to getBlocksByName. ([58156](https://github.com/WordPress/gutenberg/pull/58156))
+- Scripts: Remove unused variable in bin/list-experimental-api-matches.sh. ([57771](https://github.com/WordPress/gutenberg/pull/57771))
+- Shadows: Prevent empty style object when removing shadow. ([58155](https://github.com/WordPress/gutenberg/pull/58155))
+- [Fonts API] removing files and files loading no longer needed. ([57972](https://github.com/WordPress/gutenberg/pull/57972))
+
+#### Components
+- PaletteEdit: Improve unit tests. ([57645](https://github.com/WordPress/gutenberg/pull/57645))
+- Tooltip and Button: Tidy up unit tests. ([57975](https://github.com/WordPress/gutenberg/pull/57975))
+- Tooltip: Add test for classname leakage. ([58182](https://github.com/WordPress/gutenberg/pull/58182))
+
+#### Block Editor
+- Soft deprecate custom 'pure' HoC in favor of 'React.memo'. ([57173](https://github.com/WordPress/gutenberg/pull/57173))
+- Stabilise RecursionProvider and useHasRecursion APIs. ([58120](https://github.com/WordPress/gutenberg/pull/58120))
+- Tidy up block patterns selectors. ([57913](https://github.com/WordPress/gutenberg/pull/57913))
+
+#### Block Library
+- Gallery Block: Remove duplicate return statement. ([57746](https://github.com/WordPress/gutenberg/pull/57746))
+- Navigation: Move the renderer class to the main navigation file. ([57979](https://github.com/WordPress/gutenberg/pull/57979))
+
+#### Font Library
+- Remove WP_Font_Family class that is no longer used. ([58184](https://github.com/WordPress/gutenberg/pull/58184))
+
+#### Block Directory
+- DownloadableBlocksPanel: Remove withSelect in favor of useSelect. ([58109](https://github.com/WordPress/gutenberg/pull/58109))
+
+#### Patterns
+- Stabilize the pattern overrides block context. ([58102](https://github.com/WordPress/gutenberg/pull/58102))
+
+#### Block API
+- Block Bindings: Remove the experimental flag. ([58089](https://github.com/WordPress/gutenberg/pull/58089))
+
+#### Post Editor
+- Editor: Use hooks instead of HoCs in 'PostScheduleCheck'. ([57833](https://github.com/WordPress/gutenberg/pull/57833))
+
+#### Script Modules API
+- Update the code and move it to the compat/wordpress-6.5 folder. ([57778](https://github.com/WordPress/gutenberg/pull/57778))
+
+#### Data Views
+- Remove obsolete check from dataviews modal actions title. ([57753](https://github.com/WordPress/gutenberg/pull/57753))
+
+
+### Tools
+
+- (chore) Revert bump to the v17.5.1 (draft) due to bug in the release found by manual testing. ([58027](https://github.com/WordPress/gutenberg/pull/58027))
+- Automate creation of Issue for major release PHP synchronisation. ([57890](https://github.com/WordPress/gutenberg/pull/57890))
+- Fix misplaced ReactRefreshWebpackPlugin. ([57777](https://github.com/WordPress/gutenberg/pull/57777))
+
+#### Testing
+- Add `setGutenbergExperiments` to `requestUtils`. ([56663](https://github.com/WordPress/gutenberg/pull/56663))
+- Add: End to end test to content locking stop editing as blocks behavior. ([57812](https://github.com/WordPress/gutenberg/pull/57812))
+- Attempt to fix php unit tests (variations api change). ([58090](https://github.com/WordPress/gutenberg/pull/58090))
+- Migrate 'block grouping' end-to-end tests to Playwright. ([57684](https://github.com/WordPress/gutenberg/pull/57684))
+- Migrate 'embedding' end-to-end tests to Playwright. ([57969](https://github.com/WordPress/gutenberg/pull/57969))
+- Migrate 'typewriter' end-to-end tests to Playwright. ([57673](https://github.com/WordPress/gutenberg/pull/57673))
+- Remove unused Navigation block end-to-end test fixtures. ([57848](https://github.com/WordPress/gutenberg/pull/57848))
+
+#### Build Tooling
+- Update caniuse-lite package. ([58087](https://github.com/WordPress/gutenberg/pull/58087))
+- Update the cherry pick script to work with the new version of gh. ([57917](https://github.com/WordPress/gutenberg/pull/57917))
+
+
+### Various
+
+- Interactivity API: Fix data-wp-on-document flaky test. ([58008](https://github.com/WordPress/gutenberg/pull/58008))
+- Interactivity API: Fix flaky test on-window. ([58134](https://github.com/WordPress/gutenberg/pull/58134))
+- Pattern Categories: Fix capitalization. ([58112](https://github.com/WordPress/gutenberg/pull/58112))
+- Remove check-latest-npm validation. ([57797](https://github.com/WordPress/gutenberg/pull/57797))
+
+#### Interactivity API
+- Create `@wordpress/interactivity-router` module. ([57924](https://github.com/WordPress/gutenberg/pull/57924))
+- Fix flaky test on-window, remove duplicate expect on-document. ([58181](https://github.com/WordPress/gutenberg/pull/58181))
+- Remove `data-wp-slot` and `data-wp-fill`. ([57854](https://github.com/WordPress/gutenberg/pull/57854))
+- Remove unused `state` and rename `props` to `attributes` in `getElement()`. ([57974](https://github.com/WordPress/gutenberg/pull/57974))
+
+#### Patterns
+- Remove pattern override experiment completely. ([58105](https://github.com/WordPress/gutenberg/pull/58105))
+- Update pattern overrides to use a hard coded support array. ([57912](https://github.com/WordPress/gutenberg/pull/57912))
+
+#### Data Views
+- Dataviews: Add Bulk actions to page. ([57826](https://github.com/WordPress/gutenberg/pull/57826))
+
+#### Post Editor
+- Add description to the save panel header when nothing is checked. ([57716](https://github.com/WordPress/gutenberg/pull/57716))
+
+#### HTML API
+- Backport updates from Core. ([57022](https://github.com/WordPress/gutenberg/pull/57022))
+
+#### Block Editor
+- Video Block: Add raw transformation from `video` html. ([47159](https://github.com/WordPress/gutenberg/pull/47159))
+
+
+## First time contributors
+
+The following PRs were merged by first time contributors:
+
+- @kt-12: Call variation through callback so it's only loaded when needed - in support of trac 59969. ([56952](https://github.com/WordPress/gutenberg/pull/56952))
+- @leomuniz: [Type] Developer Documentation - Fix removeAllNotices dispatch on the removeAllNotices doc section of @wordpress/notices. ([57436](https://github.com/WordPress/gutenberg/pull/57436))
+
+
+## Contributors
+
+The following contributors merged PRs in this release:
+
+@aaronrobertshaw @afercia @ajlende @andrewserong @annezazu @artemiomorales @arthur791004 @atachibana @bacoords @bph @brookewp @c4rl0sbr4v0 @carolinan @chad1008 @ciampo @creativecoder @DAreRodz @dcalhoun @derekblank @dmsnell @draganescu @ecgan @ellatrix @fluiddot @fullofcaffeine @gaambo @geriux @getdave @glendaviesnz @gonzomir @inc2734 @jameskoster @jeryj @jffng @jorgefilipecosta @jsnajdr @kevin940726 @kt-12 @leomuniz @luisherranz @MaggieCabrera @Mamaduka @matiasbenedetto @mcsf @michalczaplinski @mikachan @mirka @ndiego @noisysocks @ntsekouras @oandregal @ockham @oguzkocer @pbking @ramonjd @richtabor @SantosGuillamot @scruffian @SiobhyB @sirreal @swissspidy @t-hamano @talldan @tellthemachines @tjcafferkey @tyxla @vcanales @youknowriad
+
+
+= 17.5.2 =
+
+## Changelog
+
+### Bug Fixes
+
+- (Preferences)(17.5.1)(fix) Remove non-core-migrated preferences from the deprecation proxy for `get` ([58153](https://github.com/WordPress/gutenberg/pull/58153))
+- (editor)(fix) Append the `edit-post-header-toolbar` class in NavigableToolbar for backward compatibility with plugin GUI injections ([58154](https://github.com/WordPress/gutenberg/pull/58154))
+
+## Contributors
+
+The following contributors merged PRs in this release:
+
+@fullofcaffeine
+
+
+
+
= 17.5.1 =
## Changelog
diff --git a/docs/getting-started/fundamentals/block-in-the-editor.md b/docs/getting-started/fundamentals/block-in-the-editor.md
index 56ba72c283bdf7..2089d8aba2f0ec 100644
--- a/docs/getting-started/fundamentals/block-in-the-editor.md
+++ b/docs/getting-started/fundamentals/block-in-the-editor.md
@@ -3,6 +3,7 @@
The Block Editor is a React Single Page Application (SPA) and every block in the editor is displayed through a React component defined in the `edit` property of the settings object used to [register the block on the client](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/#registration-of-the-block-with-javascript-client-side).
The `props` object received by the block's `Edit` React component includes:
+
- [`attributes`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#attributes) - attributes object
- [`setAttributes`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#setattributes) - method to update the attributes object
- [`isSelected`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#isselected) - boolean that communicates whether the block is currently selected
@@ -14,18 +15,21 @@ The WordPress Gutenberg project uses
A good workflow when using a component for the Block Editor is:
+
- Import the component from a WordPress package
- Add the corresponding code for the component to your project in JSX format
- Most built-in components will be used to set [block attributes](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#using-attributes-to-store-block-data), so define any necessary attributes in `block.json` and create event handlers to update those attributes with `setAttributes` in your component
- If needed, adapt the code to be serialized and stored in the database
-
-
## Block Controls: Block Toolbar and Settings Sidebar
To simplify block customization and ensure a consistent experience for users, there are a number of built-in UI patterns to help generate the editor preview.
@@ -95,7 +98,6 @@ _See the [full block example](https://github.com/WordPress/block-development-exa
Note that `BlockControls` is only visible when the block is currently selected and in visual editing mode. `BlockControls` are not shown when editing a block in HTML editing mode.
-
### Settings Sidebar
The Settings Sidebar is used to display less-often-used settings or settings that require more screen space. The Settings Sidebar should be used for **block-level settings only**.
diff --git a/docs/getting-started/fundamentals/block-wrapper.md b/docs/getting-started/fundamentals/block-wrapper.md
index 1f2404eca9b031..6582d3af3301fe 100644
--- a/docs/getting-started/fundamentals/block-wrapper.md
+++ b/docs/getting-started/fundamentals/block-wrapper.md
@@ -2,7 +2,7 @@
Each block's markup is wrapped by a container HTML tag that needs to have the proper attributes to fully work in the Block Editor and to reflect the proper block's style settings when rendered in the Block Editor and the front end. As developers, we have full control over the block's markup, and WordPress provides the tools to add the attributes that need to exist on the wrapper to our block's markup.
-Ensuring proper attributes to the block wrapper is especially important when using custom styling or features like `supports`.
+Ensuring proper attributes to the block wrapper is especially important when using custom styling or features like `supports`.
- ) }
-
- );
-}
diff --git a/packages/edit-site/src/components/page-templates/template-actions.js b/packages/edit-site/src/components/page-templates-template-parts/actions.js
similarity index 95%
rename from packages/edit-site/src/components/page-templates/template-actions.js
rename to packages/edit-site/src/components/page-templates-template-parts/actions.js
index 7029d464ca8671..7f9fff72660ff5 100644
--- a/packages/edit-site/src/components/page-templates/template-actions.js
+++ b/packages/edit-site/src/components/page-templates-template-parts/actions.js
@@ -167,8 +167,17 @@ export const deleteTemplateAction = {
export const renameTemplateAction = {
id: 'rename-template',
label: __( 'Rename' ),
- isEligible: ( template ) =>
- isTemplateRemovable( template ) && template.is_custom,
+ isEligible: ( template ) => {
+ // We can only remove templates or template parts that can be removed.
+ // Additionally in the case of templates, we can only remove custom templates.
+ if (
+ ! isTemplateRemovable( template ) ||
+ ( template.type === TEMPLATE_POST_TYPE && ! template.is_custom )
+ ) {
+ return false;
+ }
+ return true;
+ },
RenderModal: ( { items: templates, closeModal } ) => {
const template = templates[ 0 ];
const title = decodeEntities( template.title.rendered );
diff --git a/packages/edit-site/src/components/page-template-parts/add-new-template-part.js b/packages/edit-site/src/components/page-templates-template-parts/add-new-template-part.js
similarity index 100%
rename from packages/edit-site/src/components/page-template-parts/add-new-template-part.js
rename to packages/edit-site/src/components/page-templates-template-parts/add-new-template-part.js
diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates-template-parts/index.js
similarity index 65%
rename from packages/edit-site/src/components/page-templates/index.js
rename to packages/edit-site/src/components/page-templates-template-parts/index.js
index 7995ebe376b579..a1b87d3b38d885 100644
--- a/packages/edit-site/src/components/page-templates/index.js
+++ b/packages/edit-site/src/components/page-templates-template-parts/index.js
@@ -13,7 +13,7 @@ import {
VisuallyHidden,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
-import { useState, useMemo, useCallback } from '@wordpress/element';
+import { useState, useMemo, useCallback, useEffect } from '@wordpress/element';
import { useEntityRecords } from '@wordpress/core-data';
import { decodeEntities } from '@wordpress/html-entities';
import { parse } from '@wordpress/blocks';
@@ -32,11 +32,12 @@ import { privateApis as routerPrivateApis } from '@wordpress/router';
* Internal dependencies
*/
import Page from '../page';
-import Link from '../routes/link';
+import { default as Link, useLink } from '../routes/link';
import AddNewTemplate from '../add-new-template';
import { useAddedBy, AvatarImage } from '../list/added-by';
import {
TEMPLATE_POST_TYPE,
+ TEMPLATE_PART_POST_TYPE,
ENUMERATION_TYPE,
OPERATOR_IN,
OPERATOR_NOT_IN,
@@ -48,10 +49,11 @@ import {
useResetTemplateAction,
deleteTemplateAction,
renameTemplateAction,
-} from './template-actions';
+} from './actions';
import { postRevisionsAction } from '../actions';
import usePatternSettings from '../page-patterns/use-pattern-settings';
import { unlock } from '../../lock-unlock';
+import AddNewTemplatePart from './add-new-template-part';
const { ExperimentalBlockEditorProvider, useGlobalStyle } = unlock(
blockEditorPrivateApis
@@ -60,6 +62,10 @@ const { useHistory, useLocation } = unlock( routerPrivateApis );
const EMPTY_ARRAY = [];
+const SUPPORTED_LAYOUTS = window?.__experimentalAdminViews
+ ? [ LAYOUT_TABLE, LAYOUT_GRID, LAYOUT_LIST ]
+ : [ LAYOUT_TABLE, LAYOUT_GRID ];
+
const defaultConfigPerViewType = {
[ LAYOUT_TABLE ]: {
primaryField: 'title',
@@ -79,6 +85,10 @@ const DEFAULT_VIEW = {
search: '',
page: 1,
perPage: 20,
+ sort: {
+ field: 'title',
+ direction: 'asc',
+ },
// All fields are visible by default, so it's
// better to keep track of the hidden ones.
hiddenFields: [ 'preview' ],
@@ -90,19 +100,24 @@ function normalizeSearchInput( input = '' ) {
return removeAccents( input.trim().toLowerCase() );
}
-function TemplateTitle( { item, viewType } ) {
+function Title( { item, viewType } ) {
if ( viewType === LAYOUT_LIST ) {
return decodeEntities( item.title?.rendered ) || __( '(no title)' );
}
-
+ const linkProps = {
+ params: {
+ postId: item.id,
+ postType: item.type,
+ canvas: 'edit',
+ },
+ };
+ if ( item.type === TEMPLATE_PART_POST_TYPE ) {
+ linkProps.state = {
+ backPath: '/wp_template_part/all',
+ };
+ }
return (
-
+
{ decodeEntities( item.title?.rendered ) || __( '(no title)' ) }
);
@@ -125,15 +140,18 @@ function AuthorField( { item, viewType } ) {
);
}
-function TemplatePreview( { content, viewType } ) {
+function Preview( { item, viewType } ) {
const settings = usePatternSettings();
const [ backgroundColor = 'white' ] = useGlobalStyle( 'color.background' );
const blocks = useMemo( () => {
- return parse( content );
- }, [ content ] );
- if ( ! blocks?.length ) {
- return null;
- }
+ return parse( item.content.raw );
+ }, [ item.content.raw ] );
+ const { onClick } = useLink( {
+ postId: item.id,
+ postType: item.type,
+ canvas: 'edit',
+ } );
+ const isEmpty = ! blocks?.length;
// Wrap everything in a block editor provider to ensure 'styles' that are needed
// for the previews are synced between the site editor store and the block editor store.
// Additionally we need to have the `__experimentalBlockPatterns` setting in order to
@@ -147,26 +165,68 @@ function TemplatePreview( { content, viewType } ) {
className={ `page-templates-preview-field is-viewtype-${ viewType }` }
style={ { backgroundColor } }
>
-
+
+ { isEmpty &&
+ ( item.type === TEMPLATE_POST_TYPE
+ ? __( 'Empty template' )
+ : __( 'Empty template part' ) ) }
+ { ! isEmpty && }
+
);
}
-export default function DataviewsTemplates() {
+export default function PageTemplatesTemplateParts( { postType } ) {
const { params } = useLocation();
- const { layout } = params;
+ const { activeView = 'all', layout } = params;
const defaultView = useMemo( () => {
return {
...DEFAULT_VIEW,
- type: layout ?? DEFAULT_VIEW.type,
+ type: window?.__experimentalAdminViews
+ ? layout ?? DEFAULT_VIEW.type
+ : DEFAULT_VIEW.type,
+ filters:
+ activeView !== 'all'
+ ? [
+ {
+ field: 'author',
+ operator: 'in',
+ value: activeView,
+ },
+ ]
+ : [],
};
- }, [ layout ] );
+ }, [ layout, activeView ] );
const [ view, setView ] = useState( defaultView );
- const { records: allTemplates, isResolving: isLoadingData } =
- useEntityRecords( 'postType', TEMPLATE_POST_TYPE, {
+ useEffect( () => {
+ setView( ( currentView ) => ( {
+ ...currentView,
+ filters:
+ activeView !== 'all'
+ ? [
+ {
+ field: 'author',
+ operator: 'in',
+ value: activeView,
+ },
+ ]
+ : [],
+ } ) );
+ }, [ activeView ] );
+
+ const { records, isResolving: isLoadingData } = useEntityRecords(
+ 'postType',
+ postType,
+ {
per_page: -1,
- } );
+ }
+ );
const history = useHistory();
const onSelectionChange = useCallback(
( items ) => {
@@ -181,47 +241,47 @@ export default function DataviewsTemplates() {
);
const authors = useMemo( () => {
- if ( ! allTemplates ) {
+ if ( ! records ) {
return EMPTY_ARRAY;
}
const authorsSet = new Set();
- allTemplates.forEach( ( template ) => {
+ records.forEach( ( template ) => {
authorsSet.add( template.author_text );
} );
return Array.from( authorsSet ).map( ( author ) => ( {
value: author,
label: author,
} ) );
- }, [ allTemplates ] );
+ }, [ records ] );
- const fields = useMemo(
- () => [
+ const fields = useMemo( () => {
+ const _fields = [
{
header: __( 'Preview' ),
id: 'preview',
render: ( { item } ) => {
- return (
-
- );
+ return ;
},
minWidth: 120,
maxWidth: 120,
enableSorting: false,
},
{
- header: __( 'Template' ),
+ header:
+ postType === TEMPLATE_POST_TYPE
+ ? __( 'Template' )
+ : __( 'Template Part' ),
id: 'title',
getValue: ( { item } ) => item.title?.rendered,
render: ( { item } ) => (
-
+
),
maxWidth: 400,
enableHiding: false,
},
- {
+ ];
+ if ( postType === TEMPLATE_POST_TYPE ) {
+ _fields.push( {
header: __( 'Description' ),
id: 'description',
getValue: ( { item } ) => item.description,
@@ -246,35 +306,37 @@ export default function DataviewsTemplates() {
maxWidth: 400,
minWidth: 320,
enableSorting: false,
+ } );
+ }
+ // TODO: The plan is to support fields reordering, which would require an API like `order` or something
+ // similar. With the aforementioned API we wouldn't need to construct the fields array like this.
+ _fields.push( {
+ header: __( 'Author' ),
+ id: 'author',
+ getValue: ( { item } ) => item.author_text,
+ render: ( { item } ) => {
+ return ;
},
- {
- header: __( 'Author' ),
- id: 'author',
- getValue: ( { item } ) => item.author_text,
- render: ( { item } ) => {
- return ;
- },
- enableHiding: false,
- type: ENUMERATION_TYPE,
- elements: authors,
- width: '1%',
- },
- ],
- [ authors, view.type ]
- );
+ enableHiding: false,
+ type: ENUMERATION_TYPE,
+ elements: authors,
+ width: '1%',
+ } );
+ return _fields;
+ }, [ postType, authors, view.type ] );
const { data, paginationInfo } = useMemo( () => {
- if ( ! allTemplates ) {
+ if ( ! records ) {
return {
data: EMPTY_ARRAY,
paginationInfo: { totalItems: 0, totalPages: 0 },
};
}
- let filteredTemplates = [ ...allTemplates ];
+ let filteredData = [ ...records ];
// Handle global search.
if ( view.search ) {
const normalizedSearch = normalizeSearchInput( view.search );
- filteredTemplates = filteredTemplates.filter( ( item ) => {
+ filteredData = filteredData.filter( ( item ) => {
const title = item.title?.rendered || item.slug;
return (
normalizeSearchInput( title ).includes(
@@ -295,7 +357,7 @@ export default function DataviewsTemplates() {
filter.operator === OPERATOR_IN &&
!! filter.value
) {
- filteredTemplates = filteredTemplates.filter( ( item ) => {
+ filteredData = filteredData.filter( ( item ) => {
return item.author_text === filter.value;
} );
} else if (
@@ -303,7 +365,7 @@ export default function DataviewsTemplates() {
filter.operator === OPERATOR_NOT_IN &&
!! filter.value
) {
- filteredTemplates = filteredTemplates.filter( ( item ) => {
+ filteredData = filteredData.filter( ( item ) => {
return item.author_text !== filter.value;
} );
}
@@ -312,19 +374,19 @@ export default function DataviewsTemplates() {
// Handle sorting.
if ( view.sort ) {
- filteredTemplates = sortByTextFields( {
- data: filteredTemplates,
+ filteredData = sortByTextFields( {
+ data: filteredData,
view,
fields,
- textFields: [ 'title' ],
+ textFields: [ 'title', 'author' ],
} );
}
// Handle pagination.
return getPaginationResults( {
- data: filteredTemplates,
+ data: filteredData,
view,
} );
- }, [ allTemplates, view, fields ] );
+ }, [ records, view, fields ] );
const resetTemplateAction = useResetTemplateAction();
const actions = useMemo(
@@ -360,13 +422,21 @@ export default function DataviewsTemplates() {
return (
+ postType === TEMPLATE_POST_TYPE ? (
+
+ ) : (
+
+ )
}
>
);
diff --git a/packages/edit-site/src/components/page-templates-template-parts/style.scss b/packages/edit-site/src/components/page-templates-template-parts/style.scss
new file mode 100644
index 00000000000000..72e12d9808dddf
--- /dev/null
+++ b/packages/edit-site/src/components/page-templates-template-parts/style.scss
@@ -0,0 +1,44 @@
+.page-templates-preview-field {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ border-radius: 3px 3px 0 0;
+
+ .page-templates-preview-field__button {
+ box-shadow: none;
+ border: none;
+ padding: 0;
+ background-color: unset;
+ box-sizing: border-box;
+ cursor: pointer;
+ overflow: hidden;
+ height: 100%;
+ border-radius: 3px;
+
+ &:focus-visible {
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
+ // Windows High Contrast mode will show this outline, but not the box-shadow.
+ outline: 2px solid transparent;
+ }
+ }
+
+ &.is-viewtype-list {
+ .block-editor-block-preview__container {
+ height: 120px;
+ }
+ }
+
+ &.is-viewtype-grid {
+ .block-editor-block-preview__container {
+ height: auto;
+ }
+
+ .page-templates-preview-field__button {
+ border-radius: 3px 3px 0 0;
+ }
+ }
+}
+
+.page-templates-description {
+ white-space: normal;
+}
diff --git a/packages/edit-site/src/components/page-templates/style.scss b/packages/edit-site/src/components/page-templates/style.scss
deleted file mode 100644
index 9fdca6d15d5600..00000000000000
--- a/packages/edit-site/src/components/page-templates/style.scss
+++ /dev/null
@@ -1,17 +0,0 @@
-.page-templates-preview-field {
- &.is-viewtype-list {
- .block-editor-block-preview__container {
- height: 120px;
- }
- }
-
- &.is-viewtype-grid {
- .block-editor-block-preview__container {
- height: auto;
- }
- }
-}
-
-.page-templates-description {
- white-space: normal;
-}
diff --git a/packages/edit-site/src/components/page/style.scss b/packages/edit-site/src/components/page/style.scss
index fd69544bb6cd7f..ef76f4fc22e26e 100644
--- a/packages/edit-site/src/components/page/style.scss
+++ b/packages/edit-site/src/components/page/style.scss
@@ -1,14 +1,7 @@
.edit-site-page {
color: $gray-800;
background: $white;
- flex-grow: 1;
- overflow: hidden;
- margin: 0;
- margin-top: $header-height;
- @include break-medium() {
- border-radius: 8px;
- margin: $canvas-padding $canvas-padding $canvas-padding 0;
- }
+ height: 100%;
}
.edit-site-page-header {
diff --git a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js
index cbcb4f2f8ed59c..586d1b602e0df2 100644
--- a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js
+++ b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js
@@ -29,7 +29,7 @@ export default function DataViewItem( {
suffix,
} ) {
const {
- params: { path },
+ params: { path, layout },
} = useLocation();
const iconToUse =
@@ -37,6 +37,7 @@ export default function DataViewItem( {
const linkInfo = useLink( {
path,
+ layout,
activeView: isCustom === 'true' ? customViewId : slug,
isCustom,
} );
diff --git a/packages/edit-site/src/components/sidebar-dataviews/default-views.js b/packages/edit-site/src/components/sidebar-dataviews/default-views.js
index 329103bd2b97d2..d6e7ebe72df21a 100644
--- a/packages/edit-site/src/components/sidebar-dataviews/default-views.js
+++ b/packages/edit-site/src/components/sidebar-dataviews/default-views.js
@@ -29,7 +29,7 @@ export const DEFAULT_CONFIG_PER_VIEW_TYPE = {
};
const DEFAULT_PAGE_BASE = {
- type: LAYOUT_LIST,
+ type: LAYOUT_TABLE,
search: '',
filters: [],
page: 1,
diff --git a/packages/edit-site/src/components/sidebar-dataviews/index.js b/packages/edit-site/src/components/sidebar-dataviews/index.js
index 9748600907e331..caf838b0857866 100644
--- a/packages/edit-site/src/components/sidebar-dataviews/index.js
+++ b/packages/edit-site/src/components/sidebar-dataviews/index.js
@@ -15,7 +15,7 @@ import DataViewItem from './dataview-item';
import CustomDataViewsList from './custom-dataviews-list';
const PATH_TO_TYPE = {
- '/page': 'page',
+ '/pages': 'page',
};
export default function DataViewsSidebarContent() {
@@ -47,11 +47,13 @@ export default function DataViewsSidebarContent() {
);
} ) }
-
+ { window?.__experimentalAdminViews && (
+
+ ) }
>
);
}
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js
index ade28989ff9557..3bb3c9c0c8fe02 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js
@@ -78,6 +78,7 @@ export default function SidebarNavigationScreenPagesDataViews() {
title={ __( 'Pages' ) }
description={ __( 'Browse and manage pages.' ) }
content={ }
+ backPath="/page"
footer={
{ templates?.map( ( item ) => (
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js
index 85eaeea14a536f..82fc6d0fa2412b 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js
@@ -137,14 +137,6 @@ export default function SidebarNavigationScreenPages() {
};
const pagesLink = useLink( { path: '/pages' } );
- const manageAllPagesProps = window?.__experimentalAdminViews
- ? { ...pagesLink }
- : {
- href: 'edit.php?post_type=page',
- onClick: () => {
- document.location = 'edit.php?post_type=page';
- },
- };
return (
<>
@@ -230,7 +222,7 @@ export default function SidebarNavigationScreenPages() {
) ) }
{ __( 'Manage all pages' ) }
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content.js b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content.js
new file mode 100644
index 00000000000000..2188fade413dbb
--- /dev/null
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/content.js
@@ -0,0 +1,73 @@
+/**
+ * WordPress dependencies
+ */
+import { useEntityRecords } from '@wordpress/core-data';
+import { useMemo } from '@wordpress/element';
+import { __experimentalItemGroup as ItemGroup } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import DataViewItem from '../sidebar-dataviews/dataview-item';
+import { useAddedBy } from '../list/added-by';
+import { layout } from '@wordpress/icons';
+
+const EMPTY_ARRAY = [];
+
+function TemplateDataviewItem( { template, isActive } ) {
+ const { text, icon } = useAddedBy( template.type, template.id );
+ return (
+
+ );
+}
+
+export default function DataviewsTemplatesSidebarContent( {
+ activeView,
+ postType,
+ config,
+} ) {
+ const { records } = useEntityRecords( 'postType', postType, {
+ per_page: -1,
+ } );
+ const firstItemPerAuthorText = useMemo( () => {
+ const firstItemPerAuthor = records?.reduce( ( acc, template ) => {
+ const author = template.author_text;
+ if ( author && ! acc[ author ] ) {
+ acc[ author ] = template;
+ }
+ return acc;
+ }, {} );
+ return (
+ ( firstItemPerAuthor && Object.values( firstItemPerAuthor ) ) ??
+ EMPTY_ARRAY
+ );
+ }, [ records ] );
+
+ return (
+
+
+ { firstItemPerAuthorText.map( ( template ) => {
+ return (
+
+ );
+ } ) }
+
+ );
+}
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js
index a07e08a44f8b41..a97bb8e8030594 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates-browse/index.js
@@ -3,6 +3,7 @@
*/
import { __ } from '@wordpress/i18n';
import { useSelect } from '@wordpress/data';
+
import { __experimentalUseNavigator as useNavigator } from '@wordpress/components';
import { privateApis as routerPrivateApis } from '@wordpress/router';
@@ -16,6 +17,7 @@ import {
TEMPLATE_PART_POST_TYPE,
} from '../../utils/constants';
import { unlock } from '../../lock-unlock';
+import DataviewsTemplatesSidebarContent from './content';
const config = {
[ TEMPLATE_POST_TYPE ]: {
@@ -40,7 +42,7 @@ export default function SidebarNavigationScreenTemplatesBrowse() {
params: { postType },
} = useNavigator();
const {
- params: { didAccessPatternsPage },
+ params: { didAccessPatternsPage, activeView = 'all' },
} = useLocation();
const isTemplatePartsMode = useSelect( ( select ) => {
@@ -56,6 +58,13 @@ export default function SidebarNavigationScreenTemplatesBrowse() {
title={ config[ postType ].title }
description={ config[ postType ].description }
backPath={ config[ postType ].backPath }
+ content={
+
+ }
/>
);
}
diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js
index 73c6aea7e328c5..19cd55ae6fac2d 100644
--- a/packages/edit-site/src/components/sidebar/index.js
+++ b/packages/edit-site/src/components/sidebar/index.js
@@ -66,11 +66,10 @@ function SidebarScreens() {
- { window?.__experimentalAdminViews ? (
-
- ) : (
-
- ) }
+
+
+
+
diff --git a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js
index 4c2214b76abd3c..839996e2ebdf9c 100644
--- a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js
+++ b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js
@@ -160,7 +160,7 @@ function useResolveEditedEntityAndContext( { path, postId, postType } ) {
}
// Some URLs in list views are different
- if ( path === '/page' && postId ) {
+ if ( path === '/pages' && postId ) {
return resolveTemplateForPostTypeAndId( 'page', postId );
}
@@ -196,7 +196,7 @@ function useResolveEditedEntityAndContext( { path, postId, postType } ) {
}
// Some URLs in list views are different
- if ( path === '/page' && postId ) {
+ if ( path === '/pages' && postId ) {
return { postType: 'page', postId };
}
@@ -211,6 +211,10 @@ function useResolveEditedEntityAndContext( { path, postId, postType } ) {
return { isReady: true, postType: 'wp_template', postId, context };
}
+ if ( path === '/wp_template_part/all' && postId ) {
+ return { isReady: true, postType: 'wp_template_part', postId, context };
+ }
+
if ( postTypesWithoutParentTemplate.includes( postType ) ) {
return { isReady: true, postType, postId, context };
}
diff --git a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js
index 3eb6d6e6f4fadf..3f57a62fba7287 100644
--- a/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js
+++ b/packages/edit-site/src/components/sync-state-with-url/use-sync-path-with-url.js
@@ -101,15 +101,28 @@ export default function useSyncPathWithURL() {
canvas: undefined,
path: navigatorLocation.path,
} );
+ } else if (
+ navigatorLocation.path === '/wp_template/all' &&
+ ! window?.__experimentalAdminViews
+ ) {
+ // When the experiment is disabled, we only support table layout.
+ // Clear it out from the URL, so layouts other than table cannot be accessed.
+ updateUrlParams( {
+ postType: undefined,
+ categoryType: undefined,
+ categoryId: undefined,
+ path: navigatorLocation.path,
+ layout: undefined,
+ } );
} else if (
// These sidebar paths are special in the sense that the url in these pages may or may not have a postId and we need to retain it if it has.
// The "type" property should be kept as well.
- ( navigatorLocation.path === '/page' &&
- window?.__experimentalAdminViews ) ||
- ( navigatorLocation.path === '/wp_template' &&
+ ( navigatorLocation.path === '/pages' &&
window?.__experimentalAdminViews ) ||
( navigatorLocation.path === '/wp_template/all' &&
- ! window?.__experimentalAdminViews )
+ window?.__experimentalAdminViews ) ||
+ ( navigatorLocation.path === '/wp_template_part/all' &&
+ window?.__experimentalAdminViews )
) {
updateUrlParams( {
postType: undefined,
diff --git a/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js b/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js
index 0265329f40b095..0febc2379a8b4c 100644
--- a/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js
+++ b/packages/edit-site/src/hooks/commands/use-edit-mode-commands.js
@@ -216,6 +216,8 @@ function useEditUICommands() {
showBlockBreadcrumbs,
isListViewOpen,
isDistractionFree,
+ isTopToolbar,
+ isFocusMode,
} = useSelect( ( select ) => {
const { get } = select( preferencesStore );
const { getEditorMode } = select( editSiteStore );
@@ -229,6 +231,8 @@ function useEditUICommands() {
showBlockBreadcrumbs: get( 'core', 'showBlockBreadcrumbs' ),
isListViewOpen: isListViewOpened(),
isDistractionFree: get( 'core', 'distractionFree' ),
+ isFocusMode: get( 'core', 'focusMode' ),
+ isTopToolbar: get( 'core', 'fixedToolbar' ),
};
}, [] );
const { openModal } = useDispatch( interfaceStore );
@@ -271,16 +275,33 @@ function useEditUICommands() {
commands.push( {
name: 'core/toggle-spotlight-mode',
- label: __( 'Toggle spotlight mode' ),
+ label: __( 'Toggle spotlight' ),
callback: ( { close } ) => {
toggle( 'core', 'focusMode' );
close();
+ createInfoNotice(
+ isFocusMode ? __( 'Spotlight off.' ) : __( 'Spotlight on.' ),
+ {
+ id: 'core/edit-site/toggle-spotlight-mode/notice',
+ type: 'snackbar',
+ actions: [
+ {
+ label: __( 'Undo' ),
+ onClick: () => {
+ toggle( 'core', 'focusMode' );
+ },
+ },
+ ],
+ }
+ );
},
} );
commands.push( {
name: 'core/toggle-distraction-free',
- label: __( 'Toggle distraction free' ),
+ label: isDistractionFree
+ ? __( 'Exit Distraction Free' )
+ : __( 'Enter Distraction Free ' ),
callback: ( { close } ) => {
toggleDistractionFree();
close();
@@ -296,6 +317,23 @@ function useEditUICommands() {
toggleDistractionFree();
}
close();
+ createInfoNotice(
+ isTopToolbar
+ ? __( 'Top toolbar off.' )
+ : __( 'Top toolbar on.' ),
+ {
+ id: 'core/edit-site/toggle-top-toolbar/notice',
+ type: 'snackbar',
+ actions: [
+ {
+ label: __( 'Undo' ),
+ onClick: () => {
+ toggle( 'core', 'fixedToolbar' );
+ },
+ },
+ ],
+ }
+ );
},
} );
@@ -350,11 +388,20 @@ function useEditUICommands() {
commands.push( {
name: 'core/toggle-list-view',
- label: __( 'Toggle list view' ),
+ label: isListViewOpen
+ ? __( 'Close List View' )
+ : __( 'Open List View' ),
icon: listView,
callback: ( { close } ) => {
setIsListViewOpened( ! isListViewOpen );
close();
+ createInfoNotice(
+ isListViewOpen ? __( 'List View off.' ) : __( 'List View on.' ),
+ {
+ id: 'core/edit-site/toggle-list-view/notice',
+ type: 'snackbar',
+ }
+ );
},
} );
diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js
index f5f4ae7ba3faee..728081bf2fc0f4 100644
--- a/packages/edit-site/src/store/actions.js
+++ b/packages/edit-site/src/store/actions.js
@@ -582,6 +582,16 @@ export const toggleDistractionFree =
{
id: 'core/edit-site/distraction-free-mode/notice',
type: 'snackbar',
+ actions: [
+ {
+ label: __( 'Undo' ),
+ onClick: () => {
+ registry
+ .dispatch( preferencesStore )
+ .toggle( 'core', 'distractionFree' );
+ },
+ },
+ ],
}
);
} );
diff --git a/packages/edit-site/src/store/private-actions.js b/packages/edit-site/src/store/private-actions.js
index 71f35dc66399ee..930e89c6254102 100644
--- a/packages/edit-site/src/store/private-actions.js
+++ b/packages/edit-site/src/store/private-actions.js
@@ -9,6 +9,11 @@ import { store as noticesStore } from '@wordpress/notices';
import { __, sprintf } from '@wordpress/i18n';
import { decodeEntities } from '@wordpress/html-entities';
+/**
+ * Internal dependencies
+ */
+import { TEMPLATE_POST_TYPE } from '../utils/constants';
+
/**
* Action that switches the canvas mode.
*
@@ -57,19 +62,20 @@ export const setEditorCanvasContainerView =
/**
* Action that removes an array of templates.
*
- * @param {Array} templates An array of template objects to remove.
+ * @param {Array} items An array of template or template part objects to remove.
*/
export const removeTemplates =
- ( templates ) =>
+ ( items ) =>
async ( { registry } ) => {
+ const isTemplate = items[ 0 ].type === TEMPLATE_POST_TYPE;
const promiseResult = await Promise.allSettled(
- templates.map( ( template ) => {
+ items.map( ( item ) => {
return registry
.dispatch( coreStore )
.deleteEntityRecord(
'postType',
- template.type,
- template.id,
+ item.type,
+ item.id,
{ force: true },
{ throwOnError: true }
);
@@ -80,20 +86,22 @@ export const removeTemplates =
if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) {
let successMessage;
- if ( templates.length === 1 ) {
+ if ( items.length === 1 ) {
// Depending on how the entity was retrieved its title might be
// an object or simple string.
- const templateTitle =
- typeof templates[ 0 ].title === 'string'
- ? templates[ 0 ].title
- : templates[ 0 ].title?.rendered;
+ const title =
+ typeof items[ 0 ].title === 'string'
+ ? items[ 0 ].title
+ : items[ 0 ].title?.rendered;
successMessage = sprintf(
/* translators: The template/part's name. */
__( '"%s" deleted.' ),
- decodeEntities( templateTitle )
+ decodeEntities( title )
);
} else {
- successMessage = __( 'Templates deleted.' );
+ successMessage = isTemplate
+ ? __( 'Templates deleted.' )
+ : __( 'Template parts deleted.' );
}
registry
@@ -110,9 +118,11 @@ export const removeTemplates =
if ( promiseResult[ 0 ].reason?.message ) {
errorMessage = promiseResult[ 0 ].reason.message;
} else {
- errorMessage = __(
- 'An error occurred while deleting the template.'
- );
+ errorMessage = isTemplate
+ ? __( 'An error occurred while deleting the template.' )
+ : __(
+ 'An error occurred while deleting the template part.'
+ );
}
// If we were trying to delete a multiple templates
} else {
@@ -126,25 +136,45 @@ export const removeTemplates =
}
}
if ( errorMessages.size === 0 ) {
- errorMessage = __(
- 'An error occurred while deleting the templates.'
- );
+ errorMessage = isTemplate
+ ? __(
+ 'An error occurred while deleting the templates.'
+ )
+ : __(
+ 'An error occurred while deleting the template parts.'
+ );
} else if ( errorMessages.size === 1 ) {
- errorMessage = sprintf(
- /* translators: %s: an error message */
- __(
- 'An error occurred while deleting the templates: %s'
- ),
- [ ...errorMessages ][ 0 ]
- );
+ errorMessage = isTemplate
+ ? sprintf(
+ /* translators: %s: an error message */
+ __(
+ 'An error occurred while deleting the templates: %s'
+ ),
+ [ ...errorMessages ][ 0 ]
+ )
+ : sprintf(
+ /* translators: %s: an error message */
+ __(
+ 'An error occurred while deleting the template parts: %s'
+ ),
+ [ ...errorMessages ][ 0 ]
+ );
} else {
- errorMessage = sprintf(
- /* translators: %s: a list of comma separated error messages */
- __(
- 'Some errors occurred while deleting the templates: %s'
- ),
- [ ...errorMessages ].join( ',' )
- );
+ errorMessage = isTemplate
+ ? sprintf(
+ /* translators: %s: a list of comma separated error messages */
+ __(
+ 'Some errors occurred while deleting the templates: %s'
+ ),
+ [ ...errorMessages ].join( ',' )
+ )
+ : sprintf(
+ /* translators: %s: a list of comma separated error messages */
+ __(
+ 'Some errors occurred while deleting the template parts: %s'
+ ),
+ [ ...errorMessages ].join( ',' )
+ );
}
}
registry
diff --git a/packages/edit-site/src/store/selectors.js b/packages/edit-site/src/store/selectors.js
index becc066a45d248..2bc26386827cc4 100644
--- a/packages/edit-site/src/store/selectors.js
+++ b/packages/edit-site/src/store/selectors.js
@@ -250,9 +250,7 @@ export const getCurrentTemplateTemplateParts = createRegistrySelector(
);
const clientIds =
- select( blockEditorStore ).__experimentalGetGlobalBlocksByName(
- 'core/template-part'
- );
+ select( blockEditorStore ).getBlocksByName( 'core/template-part' );
const blocks =
select( blockEditorStore ).getBlocksByClientId( clientIds );
diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss
index 164a8523b19628..e6cb953b0b1861 100644
--- a/packages/edit-site/src/style.scss
+++ b/packages/edit-site/src/style.scss
@@ -12,7 +12,7 @@
@import "./components/page/style.scss";
@import "./components/page-pages/style.scss";
@import "./components/page-patterns/style.scss";
-@import "./components/page-templates/style.scss";
+@import "./components/page-templates-template-parts/style.scss";
@import "./components/table/style.scss";
@import "./components/sidebar-edit-mode/style.scss";
@import "./components/sidebar-edit-mode/page-panels/style.scss";
diff --git a/packages/edit-site/src/utils/get-is-list-page.js b/packages/edit-site/src/utils/get-is-list-page.js
index 9530cd85bf04b4..2ee661253cf063 100644
--- a/packages/edit-site/src/utils/get-is-list-page.js
+++ b/packages/edit-site/src/utils/get-is-list-page.js
@@ -14,8 +14,9 @@ export default function getIsListPage(
isMobileViewport
) {
return (
- [ '/wp_template/all', '/wp_template_part/all' ].includes( path ) ||
- ( path === '/page' && window?.__experimentalAdminViews ) ||
+ [ '/wp_template/all', '/wp_template_part/all', '/pages' ].includes(
+ path
+ ) ||
( path === '/patterns' &&
// Don't treat "/patterns" without categoryType and categoryId as a
// list page in mobile because the sidebar covers the whole page.
diff --git a/packages/edit-widgets/CHANGELOG.md b/packages/edit-widgets/CHANGELOG.md
index efef3dd674b80c..a4982dc43f2f9b 100644
--- a/packages/edit-widgets/CHANGELOG.md
+++ b/packages/edit-widgets/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 5.27.0 (2024-01-24)
+
## 5.26.0 (2024-01-10)
## 5.25.0 (2023-12-13)
diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json
index 8861148d173ca5..b2e36966443e07 100644
--- a/packages/edit-widgets/package.json
+++ b/packages/edit-widgets/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/edit-widgets",
- "version": "5.26.0",
+ "version": "5.27.0",
"description": "Widgets Page module for WordPress..",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/edit-widgets/src/components/notices/style.scss b/packages/edit-widgets/src/components/notices/style.scss
index c0b1f01836fd33..78aaecc883ba45 100644
--- a/packages/edit-widgets/src/components/notices/style.scss
+++ b/packages/edit-widgets/src/components/notices/style.scss
@@ -11,7 +11,6 @@
.edit-widgets-notices__pinned {
.components-notice {
box-sizing: border-box;
- margin: 0;
border-bottom: $border-width solid rgba(0, 0, 0, 0.2);
padding: 0 $grid-unit-15;
diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md
index 4484201d28fcc6..c2f4498cba25cb 100644
--- a/packages/editor/CHANGELOG.md
+++ b/packages/editor/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 13.27.0 (2024-01-24)
+
## 13.26.0 (2024-01-10)
### New Features
diff --git a/packages/editor/package.json b/packages/editor/package.json
index b974c7443851f1..874a6b9282440a 100644
--- a/packages/editor/package.json
+++ b/packages/editor/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/editor",
- "version": "13.26.0",
+ "version": "13.27.0",
"description": "Enhanced block editor for WordPress posts.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
@@ -27,7 +27,7 @@
"sideEffects": [
"build-style/**",
"src/**/*.scss",
- "{src,build,build-module}/{index.js,store/index.js,hooks/**}"
+ "{src,build,build-module}/{index.js,store/index.js,hooks/**,bindings/**}"
],
"dependencies": {
"@babel/runtime": "^7.16.0",
diff --git a/packages/editor/src/bindings/index.js b/packages/editor/src/bindings/index.js
new file mode 100644
index 00000000000000..8a883e8904a71b
--- /dev/null
+++ b/packages/editor/src/bindings/index.js
@@ -0,0 +1,13 @@
+/**
+ * WordPress dependencies
+ */
+import { store as blockEditorStore } from '@wordpress/block-editor';
+import { dispatch } from '@wordpress/data';
+/**
+ * Internal dependencies
+ */
+import { unlock } from '../lock-unlock';
+import postMeta from './post-meta';
+
+const { registerBlockBindingsSource } = unlock( dispatch( blockEditorStore ) );
+registerBlockBindingsSource( postMeta );
diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js
new file mode 100644
index 00000000000000..17f5e1837e35e0
--- /dev/null
+++ b/packages/editor/src/bindings/post-meta.js
@@ -0,0 +1,42 @@
+/**
+ * WordPress dependencies
+ */
+import { useEntityProp } from '@wordpress/core-data';
+import { useSelect } from '@wordpress/data';
+import { __ } from '@wordpress/i18n';
+/**
+ * Internal dependencies
+ */
+import { store as editorStore } from '../store';
+
+export default {
+ name: 'post_meta',
+ label: __( 'Post Meta' ),
+ useSource( props, sourceAttributes ) {
+ const { getCurrentPostType } = useSelect( editorStore );
+ const { context } = props;
+ const { value: metaKey } = sourceAttributes;
+ const postType = context.postType
+ ? context.postType
+ : getCurrentPostType();
+ const [ meta, setMeta ] = useEntityProp(
+ 'postType',
+ context.postType,
+ 'meta',
+ context.postId
+ );
+
+ if ( postType === 'wp_template' ) {
+ return { placeholder: metaKey };
+ }
+ const metaValue = meta[ metaKey ];
+ const updateMetaValue = ( newValue ) => {
+ setMeta( { ...meta, [ metaKey ]: newValue } );
+ };
+ return {
+ placeholder: metaKey,
+ useValue: [ metaValue, updateMetaValue ],
+ };
+ },
+ lockAttributesEditing: true,
+};
diff --git a/packages/editor/src/components/document-tools/index.js b/packages/editor/src/components/document-tools/index.js
index cf26fc600a0385..05907654fa9b84 100644
--- a/packages/editor/src/components/document-tools/index.js
+++ b/packages/editor/src/components/document-tools/index.js
@@ -104,8 +104,16 @@ function DocumentTools( {
const shortLabel = ! isInserterOpened ? __( 'Add' ) : __( 'Close' );
return (
+ // Some plugins expect and use the `edit-post-header-toolbar` CSS class to
+ // find the toolbar and inject UI elements into it. This is not officially
+ // supported, but we're keeping it in the list of class names for backwards
+ // compatibility.
{ !! userAvatar && (
diff --git a/packages/editor/src/components/post-locked-modal/style.scss b/packages/editor/src/components/post-locked-modal/style.scss
index 9225032b6d2289..03e86642493df3 100644
--- a/packages/editor/src/components/post-locked-modal/style.scss
+++ b/packages/editor/src/components/post-locked-modal/style.scss
@@ -1,9 +1,3 @@
-.editor-post-locked-modal {
- @include break-small() {
- max-width: $break-mobile;
- }
-}
-
.editor-post-locked-modal__buttons {
margin-top: $grid-unit-30;
}
diff --git a/packages/editor/src/components/post-saved-state/index.js b/packages/editor/src/components/post-saved-state/index.js
index c3089057757d9d..ae9e03b5c300e6 100644
--- a/packages/editor/src/components/post-saved-state/index.js
+++ b/packages/editor/src/components/post-saved-state/index.js
@@ -9,7 +9,6 @@ import classnames from 'classnames';
import {
__unstableGetAnimateClassName as getAnimateClassName,
Button,
- Tooltip,
} from '@wordpress/components';
import { usePrevious, useViewportMatch } from '@wordpress/compose';
import { useDispatch, useSelect } from '@wordpress/data';
@@ -129,54 +128,38 @@ export default function PostSavedState( { forceIsDirty } ) {
text = shortLabel;
}
- const buttonAccessibleLabel = text || label;
-
- /**
- * The tooltip needs to be enabled only if the button is not disabled. When
- * relying on the internal Button tooltip functionality, this causes the
- * resulting `button` element to be always removed and re-added to the DOM,
- * causing focus loss. An alternative approach to circumvent the issue
- * is not to use the `label` and `shortcut` props on `Button` (which would
- * trigger the tooltip), and instead manually wrap the `Button` in a separate
- * `Tooltip` component.
- */
- const tooltipProps = isDisabled
- ? undefined
- : {
- text: buttonAccessibleLabel,
- shortcut: displayShortcut.primary( 's' ),
- };
-
// Use common Button instance for all saved states so that focus is not
// lost.
return (
-
- savePost() }
- variant="tertiary"
- size="compact"
- icon={ isLargeViewport ? undefined : cloudUpload }
- // Make sure the aria-label has always a value, as the default `text` is undefined on small screens.
- aria-label={ buttonAccessibleLabel }
- aria-disabled={ isDisabled }
- >
- { isSavedState && }
- { text }
-
-
+ savePost() }
+ /*
+ * We want the tooltip to show the keyboard shortcut only when the
+ * button does something, i.e. when it's not disabled.
+ */
+ shortcut={ isDisabled ? undefined : displayShortcut.primary( 's' ) }
+ variant="tertiary"
+ size="compact"
+ icon={ isLargeViewport ? undefined : cloudUpload }
+ label={ text || label }
+ aria-disabled={ isDisabled }
+ >
+ { isSavedState && }
+ { text }
+
);
}
diff --git a/packages/editor/src/components/provider/disable-non-page-content-blocks.js b/packages/editor/src/components/provider/disable-non-page-content-blocks.js
index 048b01d026c24e..48a8119350d78f 100644
--- a/packages/editor/src/components/provider/disable-non-page-content-blocks.js
+++ b/packages/editor/src/components/provider/disable-non-page-content-blocks.js
@@ -44,9 +44,9 @@ function DisableBlock( { clientId } ) {
export default function DisableNonPageContentBlocks() {
useBlockEditingMode( 'disabled' );
const clientIds = useSelect( ( select ) => {
- const { __experimentalGetGlobalBlocksByName } =
- select( blockEditorStore );
- return __experimentalGetGlobalBlocksByName( PAGE_CONTENT_BLOCK_TYPES );
+ return select( blockEditorStore ).getBlocksByName(
+ PAGE_CONTENT_BLOCK_TYPES
+ );
}, [] );
return clientIds.map( ( clientId ) => {
diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js
index 5a9b3a82b1bdbb..78c9b1a56e83a7 100644
--- a/packages/editor/src/components/provider/use-block-editor-settings.js
+++ b/packages/editor/src/components/provider/use-block-editor-settings.js
@@ -7,6 +7,7 @@ import {
store as coreStore,
__experimentalFetchLinkSuggestions as fetchLinkSuggestions,
__experimentalFetchUrlData as fetchUrlData,
+ fetchBlockPatterns,
} from '@wordpress/core-data';
import { __ } from '@wordpress/i18n';
import { store as preferencesStore } from '@wordpress/preferences';
@@ -101,7 +102,6 @@ function useBlockEditorSettings( settings, postType, postId ) {
pageOnFront,
pageForPosts,
userPatternCategories,
- restBlockPatterns,
restBlockPatternCategories,
} = useSelect(
( select ) => {
@@ -112,7 +112,6 @@ function useBlockEditorSettings( settings, postType, postId ) {
getEntityRecord,
getUserPatternCategories,
getEntityRecords,
- getBlockPatterns,
getBlockPatternCategories,
} = select( coreStore );
const { get } = select( preferencesStore );
@@ -148,7 +147,6 @@ function useBlockEditorSettings( settings, postType, postId ) {
pageOnFront: siteSettings?.page_on_front,
pageForPosts: siteSettings?.page_for_posts,
userPatternCategories: getUserPatternCategories(),
- restBlockPatterns: getBlockPatterns(),
restBlockPatternCategories: getBlockPatternCategories(),
};
},
@@ -164,22 +162,16 @@ function useBlockEditorSettings( settings, postType, postId ) {
const blockPatterns = useMemo(
() =>
- [
- ...( settingsBlockPatterns || [] ),
- ...( restBlockPatterns || [] ),
- ]
- .filter(
- ( x, index, arr ) =>
- index === arr.findIndex( ( y ) => x.name === y.name )
- )
- .filter( ( { postTypes } ) => {
+ [ ...( settingsBlockPatterns || [] ) ].filter(
+ ( { postTypes } ) => {
return (
! postTypes ||
( Array.isArray( postTypes ) &&
postTypes.includes( postType ) )
);
- } ),
- [ settingsBlockPatterns, restBlockPatterns, postType ]
+ }
+ ),
+ [ settingsBlockPatterns, postType ]
);
const blockPatternCategories = useMemo(
@@ -254,8 +246,19 @@ function useBlockEditorSettings( settings, postType, postId ) {
isDistractionFree,
keepCaretInsideBlock,
mediaUpload: hasUploadPermissions ? mediaUpload : undefined,
- __experimentalReusableBlocks: reusableBlocks,
__experimentalBlockPatterns: blockPatterns,
+ __experimentalFetchBlockPatterns: async () => {
+ return ( await fetchBlockPatterns() ).filter(
+ ( { postTypes } ) => {
+ return (
+ ! postTypes ||
+ ( Array.isArray( postTypes ) &&
+ postTypes.includes( postType ) )
+ );
+ }
+ );
+ },
+ __experimentalReusableBlocks: reusableBlocks,
__experimentalBlockPatternCategories: blockPatternCategories,
__experimentalUserPatternCategories: userPatternCategories,
__experimentalFetchLinkSuggestions: ( search, searchOptions ) =>
diff --git a/packages/editor/src/hooks/pattern-partial-syncing.js b/packages/editor/src/hooks/pattern-partial-syncing.js
index a940890dfa693a..0ddfea8d9d8e36 100644
--- a/packages/editor/src/hooks/pattern-partial-syncing.js
+++ b/packages/editor/src/hooks/pattern-partial-syncing.js
@@ -30,37 +30,41 @@ const {
*/
const withPartialSyncingControls = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
- const blockEditingMode = useBlockEditingMode();
- const isEditingPattern = useSelect(
- ( select ) =>
- select( editorStore ).getCurrentPostType() ===
- PATTERN_TYPES.user,
- []
- );
-
- const shouldShowPartialSyncingControls =
- props.isSelected &&
- isEditingPattern &&
- blockEditingMode === 'default' &&
- Object.keys( PARTIAL_SYNCING_SUPPORTED_BLOCKS ).includes(
- props.name
- );
+ const isSupportedBlock = Object.keys(
+ PARTIAL_SYNCING_SUPPORTED_BLOCKS
+ ).includes( props.name );
return (
<>
- { shouldShowPartialSyncingControls && (
-
+ { props.isSelected && isSupportedBlock && (
+
) }
>
);
}
);
-if ( window.__experimentalPatternPartialSyncing ) {
- addFilter(
- 'editor.BlockEdit',
- 'core/editor/with-partial-syncing-controls',
- withPartialSyncingControls
+// Split into a separate component to avoid a store subscription
+// on every block.
+function ControlsWithStoreSubscription( props ) {
+ const blockEditingMode = useBlockEditingMode();
+ const isEditingPattern = useSelect(
+ ( select ) =>
+ select( editorStore ).getCurrentPostType() === PATTERN_TYPES.user,
+ []
+ );
+
+ return (
+ isEditingPattern &&
+ blockEditingMode === 'default' && (
+
+ )
);
}
+
+addFilter(
+ 'editor.BlockEdit',
+ 'core/editor/with-partial-syncing-controls',
+ withPartialSyncingControls
+);
diff --git a/packages/editor/src/index.js b/packages/editor/src/index.js
index 05c04b8232907c..3f6d7a78d837c0 100644
--- a/packages/editor/src/index.js
+++ b/packages/editor/src/index.js
@@ -1,6 +1,7 @@
/**
* Internal dependencies
*/
+import './bindings';
import './hooks';
export { storeConfig, store } from './store';
diff --git a/packages/editor/src/store/private-selectors.js b/packages/editor/src/store/private-selectors.js
index e276859f884038..e8e7bcfd183536 100644
--- a/packages/editor/src/store/private-selectors.js
+++ b/packages/editor/src/store/private-selectors.js
@@ -30,7 +30,7 @@ export const getInsertionPoint = createRegistrySelector(
if ( getRenderingMode( state ) === 'template-locked' ) {
const [ postContentClientId ] =
- select( blockEditorStore ).__experimentalGetGlobalBlocksByName(
+ select( blockEditorStore ).getBlocksByName(
'core/post-content'
);
if ( postContentClientId ) {
diff --git a/packages/element/CHANGELOG.md b/packages/element/CHANGELOG.md
index e57fc1e022e451..a0d0448a5b0280 100644
--- a/packages/element/CHANGELOG.md
+++ b/packages/element/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+## 5.27.0 (2024-01-24)
+
+- Started exporting the `PureComponent` React API ([#58076](https://github.com/WordPress/gutenberg/pull/58076)).
+
## 5.26.0 (2024-01-10)
## 5.25.0 (2023-12-13)
diff --git a/packages/element/README.md b/packages/element/README.md
index 96aeeea37a462e..5636fdda56a525 100755
--- a/packages/element/README.md
+++ b/packages/element/README.md
@@ -262,6 +262,12 @@ const placeholderLabel = Platform.select( {
} );
```
+### PureComponent
+
+_Related_
+
+-
+
### RawHTML
Component used as equivalent of Fragment with unescaped HTML, in cases where it is desirable to render dangerous HTML without needing a wrapper element. To preserve additional props, a `div` wrapper _will_ be created if any props aside from `children` are passed.
diff --git a/packages/element/package.json b/packages/element/package.json
index 9f075bdfff56de..b0654bb37ba91d 100644
--- a/packages/element/package.json
+++ b/packages/element/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/element",
- "version": "5.26.0",
+ "version": "5.27.0",
"description": "Element React module for WordPress.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/element/src/react.js b/packages/element/src/react.js
index 6882f9ad3344a5..14bd4a66d2e0de 100644
--- a/packages/element/src/react.js
+++ b/packages/element/src/react.js
@@ -13,6 +13,7 @@ import {
Fragment,
isValidElement,
memo,
+ PureComponent,
StrictMode,
useCallback,
useContext,
@@ -238,6 +239,11 @@ export { lazy };
*/
export { Suspense };
+/**
+ * @see https://reactjs.org/docs/react-api.html#reactpurecomponent
+ */
+export { PureComponent };
+
/**
* Concatenate two or more React children objects.
*
diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md
index 154e8d36b3b447..b046a20e121bcd 100644
--- a/packages/env/CHANGELOG.md
+++ b/packages/env/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 9.2.0 (2024-01-24)
+
## 9.1.0 (2024-01-10)
## 9.0.0 (2023-12-13)
diff --git a/packages/env/package.json b/packages/env/package.json
index 5036479faa5323..d7912fa5132db0 100644
--- a/packages/env/package.json
+++ b/packages/env/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/env",
- "version": "9.1.0",
+ "version": "9.2.0",
"description": "A zero-config, self contained local WordPress environment for development and testing.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/escape-html/CHANGELOG.md b/packages/escape-html/CHANGELOG.md
index 8cb9c60a82c57a..5788825a97685d 100644
--- a/packages/escape-html/CHANGELOG.md
+++ b/packages/escape-html/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 2.50.0 (2024-01-24)
+
## 2.49.0 (2024-01-10)
## 2.48.0 (2023-12-13)
diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json
index cfeabcb8c06e94..3a3731207abae5 100644
--- a/packages/escape-html/package.json
+++ b/packages/escape-html/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/escape-html",
- "version": "2.49.0",
+ "version": "2.50.0",
"description": "Escape HTML utils.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md
index 10e82685d7b745..75461289eae1fc 100644
--- a/packages/eslint-plugin/CHANGELOG.md
+++ b/packages/eslint-plugin/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 17.7.0 (2024-01-24)
+
## 17.6.0 (2024-01-10)
## 17.5.0 (2023-12-13)
diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json
index b56a61ab160446..fdce2d1c4b05f1 100644
--- a/packages/eslint-plugin/package.json
+++ b/packages/eslint-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/eslint-plugin",
- "version": "17.6.0",
+ "version": "17.7.0",
"description": "ESLint plugin for WordPress development.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/format-library/CHANGELOG.md b/packages/format-library/CHANGELOG.md
index 1ea92da62e1a48..82ed194d5d8ae3 100644
--- a/packages/format-library/CHANGELOG.md
+++ b/packages/format-library/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 4.27.0 (2024-01-24)
+
## 4.26.0 (2024-01-10)
## 4.25.0 (2023-12-13)
diff --git a/packages/format-library/package.json b/packages/format-library/package.json
index 280fd5d8c20422..13837304eee69d 100644
--- a/packages/format-library/package.json
+++ b/packages/format-library/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/format-library",
- "version": "4.26.0",
+ "version": "4.27.0",
"description": "Format library for the WordPress editor.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js
index 58da13eb51ab6b..e477765009d1c8 100644
--- a/packages/format-library/src/link/inline.js
+++ b/packages/format-library/src/link/inline.js
@@ -11,16 +11,17 @@ import {
insert,
isCollapsed,
applyFormat,
- useAnchor,
removeFormat,
slice,
replace,
split,
concat,
+ useAnchor,
} from '@wordpress/rich-text';
import {
__experimentalLinkControl as LinkControl,
store as blockEditorStore,
+ useCachedTruthy,
} from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
@@ -29,7 +30,6 @@ import { useSelect } from '@wordpress/data';
*/
import { createLinkFormat, isValidHref, getFormatBoundary } from './utils';
import { link as settings } from './index';
-import useLinkInstanceKey from './use-link-instance-key';
const LINK_SETTINGS = [
...LinkControl.DEFAULT_LINK_SETTINGS,
@@ -90,12 +90,9 @@ function InlineLinkUI( {
}
function onChangeLink( nextValue ) {
- // LinkControl calls `onChange` immediately upon the toggling a setting.
- // Before merging the next value with the current link value, check if
- // the setting was toggled.
- const didToggleSetting =
- linkValue.opensInNewTab !== nextValue.opensInNewTab &&
- nextValue.url === undefined;
+ const hasLink = linkValue?.url;
+ const isNewLink = ! hasLink;
+
// Merge the next value with the current link value.
nextValue = {
...linkValue,
@@ -178,17 +175,16 @@ function InlineLinkUI( {
newValue = concat( valBefore, newValAfter );
}
- newValue.start = newValue.end;
-
- // Hides the Link UI.
- newValue.activeFormats = [];
onChange( newValue );
}
- // Focus should only be shifted back to the formatted segment when the
- // URL is submitted.
- if ( ! didToggleSetting ) {
- stopAddingLink();
+ // Focus should only be returned to the rich text on submit if this link is not
+ // being created for the first time. If it is then focus should remain within the
+ // Link UI because it should remain open for the user to modify the link they have
+ // just created.
+ if ( ! isNewLink ) {
+ const returnFocusToRichText = true;
+ stopAddingLink( returnFocusToRichText );
}
if ( ! isValidHref( newUrl ) ) {
@@ -210,11 +206,14 @@ function InlineLinkUI( {
settings,
} );
- // Generate a string based key that is unique to this anchor reference.
- // This is used to force re-mount the LinkControl component to avoid
- // potential stale state bugs caused by the component not being remounted
- // See https://github.com/WordPress/gutenberg/pull/34742.
- const forceRemountKey = useLinkInstanceKey( popoverAnchor );
+ // As you change the link by interacting with the Link UI
+ // the return value of document.getSelection jumps to the field you're editing,
+ // not the highlighted text. Given that useAnchor uses document.getSelection,
+ // it will return null, since it can't find the element within the Link UI.
+ // This caches the last truthy value of the selection anchor reference.
+ // This ensures the Popover is positioned correctly on initial submission of the link.
+ const cachedRect = useCachedTruthy( popoverAnchor.getBoundingClientRect() );
+ popoverAnchor.getBoundingClientRect = () => cachedRect;
// Focus should only be moved into the Popover when the Link is being created or edited.
// When the Link is in "preview" mode focus should remain on the rich text because at
@@ -257,10 +256,10 @@ function InlineLinkUI( {
onClose={ stopAddingLink }
onFocusOutside={ () => stopAddingLink( false ) }
placement="bottom"
+ offset={ 10 }
shift
>
);
diff --git a/packages/hooks/CHANGELOG.md b/packages/hooks/CHANGELOG.md
index 7b71f8d382f01e..5d88e9743b0fd6 100644
--- a/packages/hooks/CHANGELOG.md
+++ b/packages/hooks/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 3.50.0 (2024-01-24)
+
## 3.49.0 (2024-01-10)
## 3.48.0 (2023-12-13)
diff --git a/packages/hooks/package.json b/packages/hooks/package.json
index 08fa1eb54819ca..d56ae25b23e991 100644
--- a/packages/hooks/package.json
+++ b/packages/hooks/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/hooks",
- "version": "3.49.0",
+ "version": "3.50.0",
"description": "WordPress hooks library.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/html-entities/CHANGELOG.md b/packages/html-entities/CHANGELOG.md
index ba88048d21a5a6..af17b93cc51b24 100644
--- a/packages/html-entities/CHANGELOG.md
+++ b/packages/html-entities/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 3.50.0 (2024-01-24)
+
## 3.49.0 (2024-01-10)
## 3.48.0 (2023-12-13)
diff --git a/packages/html-entities/package.json b/packages/html-entities/package.json
index 10965eaf51faba..c0161b83e0b736 100644
--- a/packages/html-entities/package.json
+++ b/packages/html-entities/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/html-entities",
- "version": "3.49.0",
+ "version": "3.50.0",
"description": "HTML entity utilities for WordPress.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/i18n/CHANGELOG.md b/packages/i18n/CHANGELOG.md
index cca18773c660ea..05d2260013477a 100644
--- a/packages/i18n/CHANGELOG.md
+++ b/packages/i18n/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 4.50.0 (2024-01-24)
+
## 4.49.0 (2024-01-10)
## 4.48.0 (2023-12-13)
diff --git a/packages/i18n/package.json b/packages/i18n/package.json
index a2b2ed907cf966..683fa862ed1f3b 100644
--- a/packages/i18n/package.json
+++ b/packages/i18n/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/i18n",
- "version": "4.49.0",
+ "version": "4.50.0",
"description": "WordPress internationalization (i18n) library.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/icons/CHANGELOG.md b/packages/icons/CHANGELOG.md
index a5ca30a014f1db..f9b991cfc63451 100644
--- a/packages/icons/CHANGELOG.md
+++ b/packages/icons/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 9.41.0 (2024-01-24)
+
## 9.40.0 (2024-01-10)
## 9.39.0 (2023-12-13)
diff --git a/packages/icons/package.json b/packages/icons/package.json
index b94cb445b5760c..3874edd16d607d 100644
--- a/packages/icons/package.json
+++ b/packages/icons/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/icons",
- "version": "9.40.0",
+ "version": "9.41.0",
"description": "WordPress Icons package, based on dashicon.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/interactivity-router/.npmrc b/packages/interactivity-router/.npmrc
new file mode 100644
index 00000000000000..43c97e719a5a82
--- /dev/null
+++ b/packages/interactivity-router/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/packages/interactivity-router/CHANGELOG.md b/packages/interactivity-router/CHANGELOG.md
new file mode 100644
index 00000000000000..06b0eb7ad8238f
--- /dev/null
+++ b/packages/interactivity-router/CHANGELOG.md
@@ -0,0 +1,9 @@
+
+
+## Unreleased
+
+## 1.0.0 (2024-01-24)
+
+### Breaking changes
+
+- Initial version. ([57924](https://github.com/WordPress/gutenberg/pull/57924))
diff --git a/packages/interactivity-router/README.md b/packages/interactivity-router/README.md
new file mode 100644
index 00000000000000..94b88e80886c90
--- /dev/null
+++ b/packages/interactivity-router/README.md
@@ -0,0 +1,76 @@
+# Interactivity Router
+
+> **Note**
+> This package is a extension of the API shared at [Proposal: The Interactivity API – A better developer experience in building interactive blocks](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/). As part of an [Open Source project](https://developer.wordpress.org/block-editor/getting-started/faq/#the-gutenberg-project) we encourage participation in helping shape this API and the [discussions in GitHub](https://github.com/WordPress/gutenberg/discussions/categories/interactivity-api) is the best place to engage.
+
+This package defines an Interactivity API store with the `core/router` namespace, exposing state and actions like `navigate` and `prefetch` to handle client-side navigations.
+
+## Usage
+
+The package is intended to be imported dynamically in the `view.js` files of interactive blocks.
+
+```js
+import { store } from '@wordpress/interactivity';
+
+store( 'myblock', {
+ actions: {
+ *navigate( e ) {
+ e.preventDefault();
+ const { actions } = yield import(
+ '@wordpress/interactivity-router'
+ );
+ yield actions.navigate( e.target.href );
+ },
+ },
+} );
+```
+
+## Frequently Asked Questions
+
+At this point, some of the questions you have about the Interactivity API may be:
+
+### What is this?
+
+This is the base of a new standard to create interactive blocks. Read [the proposal](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/) to learn more about this.
+
+### Can I use it?
+
+You can test it, but it's still very experimental.
+
+### How do I get started?
+
+The best place to start with the Interactivity API is this [**Getting started guide**](https://github.com/WordPress/gutenberg/blob/trunk/packages/interactivity/docs/1-getting-started.md). There you'll will find a very quick start guide and the current requirements of the Interactivity API.
+
+### Where can I ask questions?
+
+The [“Interactivity API” category](https://github.com/WordPress/gutenberg/discussions/categories/interactivity-api) in Gutenberg repo discussions is the best place to ask questions about the Interactivity API.
+
+### Where can I share my feedback about the API?
+
+The [“Interactivity API” category](https://github.com/WordPress/gutenberg/discussions/categories/interactivity-api) in Gutenberg repo discussions is also the best place to share your feedback about the Interactivity API.
+
+## Installation
+
+Install the module:
+
+```bash
+npm install @wordpress/interactivity --save
+```
+
+_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for such language features and APIs, you should include [the polyfill shipped in `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill) in your code._
+
+## Docs & Examples
+
+**[Interactivity API Documentation](https://github.com/WordPress/gutenberg/tree/trunk/packages/interactivity/docs)** is the best place to learn about this proposal. Although it's still in progress, some key pages are already available:
+
+- **[Getting Started Guide](https://github.com/WordPress/gutenberg/blob/trunk/packages/interactivity/docs/1-getting-started.md)**: Follow this Getting Started guide to learn how to scaffold a new project and create your first interactive blocks.
+- **[API Reference](https://github.com/WordPress/gutenberg/blob/trunk/packages/interactivity/docs/2-api-reference.md)**: Check this page for technical detailed explanations and examples of the directives and the store.
+
+Here you have some more resources to learn/read more about the Interactivity API:
+
+- **[Interactivity API Discussions](https://github.com/WordPress/gutenberg/discussions/52882)**
+- [Proposal: The Interactivity API – A better developer experience in building interactive blocks](https://make.wordpress.org/core/2023/03/30/proposal-the-interactivity-api-a-better-developer-experience-in-building-interactive-blocks/)
+- Developer Hours sessions ([Americas](https://www.youtube.com/watch?v=RXNoyP2ZiS8&t=664s) & [APAC/EMEA](https://www.youtube.com/watch?v=6ghbrhyAcvA))
+- [wpmovies.dev](http://wpmovies.dev/) demo and its [wp-movies-demo](https://github.com/WordPress/wp-movies-demo) repo
+
+
diff --git a/packages/interactivity-router/package.json b/packages/interactivity-router/package.json
new file mode 100644
index 00000000000000..8e06056f1991fd
--- /dev/null
+++ b/packages/interactivity-router/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "@wordpress/interactivity-router",
+ "version": "1.0.0",
+ "description": "Package that exposes state and actions from the `core/router` store, part of the Interactivity API.",
+ "author": "The WordPress Contributors",
+ "license": "GPL-2.0-or-later",
+ "keywords": [
+ "wordpress",
+ "gutenberg",
+ "interactivity"
+ ],
+ "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/interactivity-router/README.md",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/WordPress/gutenberg.git",
+ "directory": "packages/interactivity-router"
+ },
+ "bugs": {
+ "url": "https://github.com/WordPress/gutenberg/labels/%5BFeature%5D%20Interactivity%20API"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "main": "build/index.js",
+ "module": "build-module/index.js",
+ "react-native": "src/index",
+ "types": "build-types",
+ "dependencies": {
+ "@wordpress/interactivity": "file:../interactivity"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/interactivity-router/src/index.js b/packages/interactivity-router/src/index.js
new file mode 100644
index 00000000000000..7396c21e9638d1
--- /dev/null
+++ b/packages/interactivity-router/src/index.js
@@ -0,0 +1,160 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ render,
+ directivePrefix,
+ toVdom,
+ getRegionRootFragment,
+ store,
+} from '@wordpress/interactivity';
+
+// The cache of visited and prefetched pages.
+const pages = new Map();
+
+// Helper to remove domain and hash from the URL. We are only interesting in
+// caching the path and the query.
+const cleanUrl = ( url ) => {
+ const u = new URL( url, window.location );
+ return u.pathname + u.search;
+};
+
+// Fetch a new page and convert it to a static virtual DOM.
+const fetchPage = async ( url, { html } ) => {
+ try {
+ if ( ! html ) {
+ const res = await window.fetch( url );
+ if ( res.status !== 200 ) return false;
+ html = await res.text();
+ }
+ const dom = new window.DOMParser().parseFromString( html, 'text/html' );
+ return regionsToVdom( dom );
+ } catch ( e ) {
+ return false;
+ }
+};
+
+// Return an object with VDOM trees of those HTML regions marked with a
+// `router-region` directive.
+const regionsToVdom = ( dom ) => {
+ const regions = {};
+ const attrName = `data-${ directivePrefix }-router-region`;
+ dom.querySelectorAll( `[${ attrName }]` ).forEach( ( region ) => {
+ const id = region.getAttribute( attrName );
+ regions[ id ] = toVdom( region );
+ } );
+ const title = dom.querySelector( 'title' )?.innerText;
+ return { regions, title };
+};
+
+// Render all interactive regions contained in the given page.
+const renderRegions = ( page ) => {
+ const attrName = `data-${ directivePrefix }-router-region`;
+ document.querySelectorAll( `[${ attrName }]` ).forEach( ( region ) => {
+ const id = region.getAttribute( attrName );
+ const fragment = getRegionRootFragment( region );
+ render( page.regions[ id ], fragment );
+ } );
+ if ( page.title ) {
+ document.title = page.title;
+ }
+};
+
+// Variable to store the current navigation.
+let navigatingTo = '';
+
+// Listen to the back and forward buttons and restore the page if it's in the
+// cache.
+window.addEventListener( 'popstate', async () => {
+ const url = cleanUrl( window.location ); // Remove hash.
+ const page = pages.has( url ) && ( await pages.get( url ) );
+ if ( page ) {
+ renderRegions( page );
+ } else {
+ window.location.reload();
+ }
+} );
+
+// Cache the current regions.
+pages.set(
+ cleanUrl( window.location ),
+ Promise.resolve( regionsToVdom( document ) )
+);
+
+export const { state, actions } = store( 'core/router', {
+ actions: {
+ /**
+ * Navigates to the specified page.
+ *
+ * This function normalizes the passed href, fetchs the page HTML if
+ * needed, and updates any interactive regions whose contents have
+ * changed. It also creates a new entry in the browser session history.
+ *
+ * @param {string} href The page href.
+ * @param {Object} [options] Options object.
+ * @param {boolean} [options.force] If true, it forces re-fetching the
+ * URL.
+ * @param {string} [options.html] HTML string to be used instead of
+ * fetching the requested URL.
+ * @param {boolean} [options.replace] If true, it replaces the current
+ * entry in the browser session
+ * history.
+ * @param {number} [options.timeout] Time until the navigation is
+ * aborted, in milliseconds. Default
+ * is 10000.
+ *
+ * @return {Promise} Promise that resolves once the navigation is
+ * completed or aborted.
+ */
+ *navigate( href, options = {} ) {
+ const url = cleanUrl( href );
+ navigatingTo = href;
+ actions.prefetch( url, options );
+
+ // Create a promise that resolves when the specified timeout ends.
+ // The timeout value is 10 seconds by default.
+ const timeoutPromise = new Promise( ( resolve ) =>
+ setTimeout( resolve, options.timeout ?? 10000 )
+ );
+
+ const page = yield Promise.race( [
+ pages.get( url ),
+ timeoutPromise,
+ ] );
+
+ // Once the page is fetched, the destination URL could have changed
+ // (e.g., by clicking another link in the meantime). If so, bail
+ // out, and let the newer execution to update the HTML.
+ if ( navigatingTo !== href ) return;
+
+ if ( page ) {
+ renderRegions( page );
+ window.history[
+ options.replace ? 'replaceState' : 'pushState'
+ ]( {}, '', href );
+ } else {
+ window.location.assign( href );
+ yield new Promise( () => {} );
+ }
+ },
+
+ /**
+ * Prefetchs the page with the passed URL.
+ *
+ * The function normalizes the URL and stores internally the fetch
+ * promise, to avoid triggering a second fetch for an ongoing request.
+ *
+ * @param {string} url The page URL.
+ * @param {Object} [options] Options object.
+ * @param {boolean} [options.force] Force fetching the URL again.
+ * @param {string} [options.html] HTML string to be used instead of
+ * fetching the requested URL.
+ */
+ prefetch( url, options = {} ) {
+ url = cleanUrl( url );
+ if ( options.force || ! pages.has( url ) ) {
+ pages.set( url, fetchPage( url, options ) );
+ }
+ },
+ },
+} );
diff --git a/packages/interactivity-router/tsconfig.json b/packages/interactivity-router/tsconfig.json
new file mode 100644
index 00000000000000..9008be9879c07f
--- /dev/null
+++ b/packages/interactivity-router/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "rootDir": "src",
+ "declarationDir": "build-types",
+ "checkJs": false,
+ "strict": false
+ },
+ "references": [ { "path": "../interactivity" } ],
+ "include": [ "src/**/*" ]
+}
diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md
index b37fff1bbb5c4c..0c627c9f640c59 100644
--- a/packages/interactivity/CHANGELOG.md
+++ b/packages/interactivity/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 4.0.0 (2024-01-24)
+
### Enhancements
- Prevent the usage of Preact components in `wp-text`. ([#57879](https://github.com/WordPress/gutenberg/pull/57879))
@@ -12,12 +14,14 @@
- Add the `data-wp-run` directive along with the `useInit` and `useWatch` hooks. ([#57805](https://github.com/WordPress/gutenberg/pull/57805))
- Add `wp-data-on-window` and `wp-data-on-document` directives. ([#57931](https://github.com/WordPress/gutenberg/pull/57931))
+- Add the `data-wp-each` directive to render lists of items using a template. ([57859](https://github.com/WordPress/gutenberg/pull/57859))
### Breaking Changes
- Remove `data-wp-slot` and `data-wp-fill`. ([#57854](https://github.com/WordPress/gutenberg/pull/57854))
- Remove `wp-data-navigation-link` directive. ([#57853](https://github.com/WordPress/gutenberg/pull/57853))
- Remove unused `state` and rename `props` to `attributes` in `getElement()`. ([#57974](https://github.com/WordPress/gutenberg/pull/57974))
+- Convert `navigate` and `prefetch` function to actions of the new `core/router` store, available when importing the `@wordpress/interactivity-router` module. ([#57924](https://github.com/WordPress/gutenberg/pull/57924))
### Bug Fix
diff --git a/packages/interactivity/docs/2-api-reference.md b/packages/interactivity/docs/2-api-reference.md
index 922662c10a8e2b..bae15e9a7fcf2f 100644
--- a/packages/interactivity/docs/2-api-reference.md
+++ b/packages/interactivity/docs/2-api-reference.md
@@ -26,6 +26,7 @@ DOM elements are connected to data stored in the state and context through direc
- [`wp-init`](#wp-init) ![](https://img.shields.io/badge/SIDE_EFFECTS-afd2e3.svg)
- [`wp-run`](#wp-run) ![](https://img.shields.io/badge/SIDE_EFFECTS-afd2e3.svg)
- [`wp-key`](#wp-key) ![](https://img.shields.io/badge/TEMPLATING-afd2e3.svg)
+ - [`wp-each`](#wp-each) ![](https://img.shields.io/badge/TEMPLATING-afd2e3.svg)
- [Values of directives are references to store properties](#values-of-directives-are-references-to-store-properties)
- [The store](#the-store)
- [Elements of the store](#elements-of-the-store)
@@ -620,6 +621,78 @@ But it can also be used on other elements:
When the list is re-rendered, the Interactivity API will match elements by their keys to determine if an item was added/removed/reordered. Elements without keys might be recreated unnecessarily.
+
+#### `wp-each`
+
+The `wp-each` directive is intended to render a list of elements. The directive can be used in `` tags, being the value a path to an array stored in the global state or the context. The content inside the `` tag is the template used to render each of the items.
+
+Each item is included in the context under the `item` name by default, so directives inside the template can access the current item.
+
+For example, let's consider the following HTML.
+
+```html
+
+
+
+
+
+```
+
+It would generate the following output:
+
+```html
+
+
hello
+
hola
+
olá
+
+```
+
+The prop that holds the item in the context can be changed by passing a suffix to the directive name. In the following example, we change the default prop `item` to `greeting`.
+
+```html
+
+
+
+
+
+```
+
+By default, it uses each element as the key for the rendered nodes, but you can also specify a path to retrieve the key if necessary, e.g., when the list contains objects.
+
+For that, you must use `data-wp-each-key` in the `` tag and not `data-wp-key` inside the template content. This is because `data-wp-each` creates
+a context provider wrapper around each rendered item, and those wrappers are the ones that need the `key` property.
+
+```html
+
+
+
+
+
+```
+
+For server-side rendered lists, another directive called `data-wp-each-child` ensures hydration works as expected. This directive is added automatically when the directive is processed on the server.
+
+```html
+
+
+
+
+
hello
+
hola
+
olá
+
+```
+
### Values of directives are references to store properties
The value assigned to a directive is a string pointing to a specific state, action, or side effect.
diff --git a/packages/interactivity/package.json b/packages/interactivity/package.json
index 35c8dd2d85bd8d..1b915b657c1a9c 100644
--- a/packages/interactivity/package.json
+++ b/packages/interactivity/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/interactivity",
- "version": "3.2.0",
+ "version": "4.0.0",
"description": "Package that provides a standard and simple way to handle the frontend interactivity of Gutenberg blocks.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/interactivity/src/directives.js b/packages/interactivity/src/directives.js
index 21ab2da29cf275..250d3bde6084c9 100644
--- a/packages/interactivity/src/directives.js
+++ b/packages/interactivity/src/directives.js
@@ -9,7 +9,7 @@ import { deepSignal, peek } from 'deepsignal';
*/
import { createPortal } from './portals';
import { useWatch, useInit } from './utils';
-import { directive } from './hooks';
+import { directive, getScope, getEvaluate } from './hooks';
const isObject = ( item ) =>
item && typeof item === 'object' && ! Array.isArray( item );
@@ -177,9 +177,11 @@ export default () => {
: name;
useInit( () => {
- // This seems necessary because Preact doesn't change the class
- // names on the hydration, so we have to do it manually. It doesn't
- // need deps because it only needs to do it the first time.
+ /*
+ * This seems necessary because Preact doesn't change the class
+ * names on the hydration, so we have to do it manually. It doesn't
+ * need deps because it only needs to do it the first time.
+ */
if ( ! result ) {
element.ref.current.classList.remove( name );
} else {
@@ -206,9 +208,11 @@ export default () => {
else element.props.style[ key ] = result;
useInit( () => {
- // This seems necessary because Preact doesn't change the styles on
- // the hydration, so we have to do it manually. It doesn't need deps
- // because it only needs to do it the first time.
+ /*
+ * This seems necessary because Preact doesn't change the styles on
+ * the hydration, so we have to do it manually. It doesn't need deps
+ * because it only needs to do it the first time.
+ */
if ( ! result ) {
element.ref.current.style.removeProperty( key );
} else {
@@ -226,24 +230,36 @@ export default () => {
const result = evaluate( entry );
element.props[ attribute ] = result;
- // This seems necessary because Preact doesn't change the attributes
- // on the hydration, so we have to do it manually. It doesn't need
- // deps because it only needs to do it the first time.
+ /*
+ * This is necessary because Preact doesn't change the attributes on the
+ * hydration, so we have to do it manually. It only needs to do it the
+ * first time. After that, Preact will handle the changes.
+ */
useInit( () => {
const el = element.ref.current;
- // We set the value directly to the corresponding
- // HTMLElement instance property excluding the following
- // special cases.
- // We follow Preact's logic: https://github.com/preactjs/preact/blob/ea49f7a0f9d1ff2c98c0bdd66aa0cbc583055246/src/diff/props.js#L110-L129
+ /*
+ * We set the value directly to the corresponding HTMLElement instance
+ * property excluding the following special cases. We follow Preact's
+ * logic: https://github.com/preactjs/preact/blob/ea49f7a0f9d1ff2c98c0bdd66aa0cbc583055246/src/diff/props.js#L110-L129
+ */
if (
attribute !== 'width' &&
attribute !== 'height' &&
attribute !== 'href' &&
attribute !== 'list' &&
attribute !== 'form' &&
- // Default value in browsers is `-1` and an empty string is
- // cast to `0` instead
+ /*
+ * The value for `tabindex` follows the parsing rules for an
+ * integer. If that fails, or if the attribute isn't present, then
+ * the browsers should "follow platform conventions to determine if
+ * the element should be considered as a focusable area",
+ * practically meaning that most elements get a default of `-1` (not
+ * focusable), but several also get a default of `0` (focusable in
+ * order after all elements with a positive `tabindex` value).
+ *
+ * @see https://html.spec.whatwg.org/#tabindex-value
+ */
attribute !== 'tabIndex' &&
attribute !== 'download' &&
attribute !== 'rowSpan' &&
@@ -259,10 +275,12 @@ export default () => {
return;
} catch ( err ) {}
}
- // aria- and data- attributes have no boolean representation.
- // A `false` value is different from the attribute not being
- // present, so we can't remove it.
- // We follow Preact's logic: https://github.com/preactjs/preact/blob/ea49f7a0f9d1ff2c98c0bdd66aa0cbc583055246/src/diff/props.js#L131C24-L136
+ /*
+ * aria- and data- attributes have no boolean representation.
+ * A `false` value is different from the attribute not being
+ * present, so we can't remove it.
+ * We follow Preact's logic: https://github.com/preactjs/preact/blob/ea49f7a0f9d1ff2c98c0bdd66aa0cbc583055246/src/diff/props.js#L131C24-L136
+ */
if (
result !== null &&
result !== undefined &&
@@ -313,4 +331,49 @@ export default () => {
directive( 'run', ( { directives: { run }, evaluate } ) => {
run.forEach( ( entry ) => evaluate( entry ) );
} );
+
+ // data-wp-each--[item]
+ directive(
+ 'each',
+ ( {
+ directives: { each, 'each-key': eachKey },
+ context: inheritedContext,
+ element,
+ evaluate,
+ } ) => {
+ if ( element.type !== 'template' ) return;
+
+ const { Provider } = inheritedContext;
+ const inheritedValue = useContext( inheritedContext );
+
+ const [ entry ] = each;
+ const { namespace, suffix } = entry;
+
+ const list = evaluate( entry );
+ return list.map( ( item ) => {
+ const mergedContext = deepSignal( {} );
+
+ const itemProp = suffix === 'default' ? 'item' : suffix;
+ const newValue = deepSignal( {
+ [ namespace ]: { [ itemProp ]: item },
+ } );
+ mergeDeepSignals( newValue, inheritedValue );
+ mergeDeepSignals( mergedContext, newValue, true );
+
+ const scope = { ...getScope(), context: mergedContext };
+ const key = eachKey
+ ? getEvaluate( { scope } )( eachKey[ 0 ] )
+ : item;
+
+ return (
+
+ { element.props.content }
+
+ );
+ } );
+ },
+ { priority: 20 }
+ );
+
+ directive( 'each-child', () => null );
};
diff --git a/packages/interactivity/src/hooks.tsx b/packages/interactivity/src/hooks.tsx
index 4309f3f0bea7a7..c133eb9981880b 100644
--- a/packages/interactivity/src/hooks.tsx
+++ b/packages/interactivity/src/hooks.tsx
@@ -261,7 +261,7 @@ const resolve = ( path, namespace ) => {
};
// Generate the evaluate function.
-const getEvaluate: GetEvaluate =
+export const getEvaluate: GetEvaluate =
( { scope } ) =>
( entry, ...args ) => {
let { value: path, namespace } = entry;
diff --git a/packages/interactivity/src/index.js b/packages/interactivity/src/index.js
index 0864291455310d..5d9165dc9920ee 100644
--- a/packages/interactivity/src/index.js
+++ b/packages/interactivity/src/index.js
@@ -2,11 +2,10 @@
* Internal dependencies
*/
import registerDirectives from './directives';
-import { init } from './router';
+import { init } from './init';
export { store } from './store';
export { directive, getContext, getElement, getNamespace } from './hooks';
-export { navigate, prefetch } from './router';
export {
withScope,
useWatch,
@@ -16,8 +15,11 @@ export {
useCallback,
useMemo,
} from './utils';
+export { directivePrefix } from './constants';
+export { toVdom } from './vdom';
+export { getRegionRootFragment } from './init';
-export { h as createElement, cloneElement } from 'preact';
+export { h as createElement, cloneElement, render } from 'preact';
export { useContext, useState, useRef } from 'preact/hooks';
export { deepSignal } from 'deepsignal';
diff --git a/packages/interactivity/src/init.js b/packages/interactivity/src/init.js
new file mode 100644
index 00000000000000..d749003b86f49d
--- /dev/null
+++ b/packages/interactivity/src/init.js
@@ -0,0 +1,35 @@
+/**
+ * External dependencies
+ */
+import { hydrate } from 'preact';
+/**
+ * Internal dependencies
+ */
+import { toVdom, hydratedIslands } from './vdom';
+import { createRootFragment } from './utils';
+import { directivePrefix } from './constants';
+
+// Keep the same root fragment for each interactive region node.
+const regionRootFragments = new WeakMap();
+export const getRegionRootFragment = ( region ) => {
+ if ( ! regionRootFragments.has( region ) ) {
+ regionRootFragments.set(
+ region,
+ createRootFragment( region.parentElement, region )
+ );
+ }
+ return regionRootFragments.get( region );
+};
+
+// Initialize the router with the initial DOM.
+export const init = async () => {
+ document
+ .querySelectorAll( `[data-${ directivePrefix }-interactive]` )
+ .forEach( ( node ) => {
+ if ( ! hydratedIslands.has( node ) ) {
+ const fragment = getRegionRootFragment( node );
+ const vdom = toVdom( node );
+ hydrate( vdom, fragment );
+ }
+ } );
+};
diff --git a/packages/interactivity/src/router.js b/packages/interactivity/src/router.js
deleted file mode 100644
index 1082d43ff3a6a6..00000000000000
--- a/packages/interactivity/src/router.js
+++ /dev/null
@@ -1,177 +0,0 @@
-/**
- * External dependencies
- */
-import { hydrate, render } from 'preact';
-/**
- * Internal dependencies
- */
-import { toVdom, hydratedIslands } from './vdom';
-import { createRootFragment } from './utils';
-import { directivePrefix } from './constants';
-
-// The cache of visited and prefetched pages.
-const pages = new Map();
-
-// Keep the same root fragment for each interactive region node.
-const regionRootFragments = new WeakMap();
-const getRegionRootFragment = ( region ) => {
- if ( ! regionRootFragments.has( region ) ) {
- regionRootFragments.set(
- region,
- createRootFragment( region.parentElement, region )
- );
- }
- return regionRootFragments.get( region );
-};
-
-// Helper to remove domain and hash from the URL. We are only interesting in
-// caching the path and the query.
-const cleanUrl = ( url ) => {
- const u = new URL( url, window.location );
- return u.pathname + u.search;
-};
-
-// Fetch a new page and convert it to a static virtual DOM.
-const fetchPage = async ( url, { html } ) => {
- try {
- if ( ! html ) {
- const res = await window.fetch( url );
- if ( res.status !== 200 ) return false;
- html = await res.text();
- }
- const dom = new window.DOMParser().parseFromString( html, 'text/html' );
- return regionsToVdom( dom );
- } catch ( e ) {
- return false;
- }
-};
-
-// Return an object with VDOM trees of those HTML regions marked with a
-// `navigation-id` directive.
-const regionsToVdom = ( dom ) => {
- const regions = {};
- const attrName = `data-${ directivePrefix }-navigation-id`;
- dom.querySelectorAll( `[${ attrName }]` ).forEach( ( region ) => {
- const id = region.getAttribute( attrName );
- regions[ id ] = toVdom( region );
- } );
- const title = dom.querySelector( 'title' )?.innerText;
- return { regions, title };
-};
-
-/**
- * Prefetchs the page with the passed URL.
- *
- * The function normalizes the URL and stores internally the fetch promise, to
- * avoid triggering a second fetch for an ongoing request.
- *
- * @param {string} url The page URL.
- * @param {Object} [options] Options object.
- * @param {boolean} [options.force] Force fetching the URL again.
- * @param {string} [options.html] HTML string to be used instead of fetching
- * the requested URL.
- */
-export const prefetch = ( url, options = {} ) => {
- url = cleanUrl( url );
- if ( options.force || ! pages.has( url ) ) {
- pages.set( url, fetchPage( url, options ) );
- }
-};
-
-// Render all interactive regions contained in the given page.
-const renderRegions = ( page ) => {
- const attrName = `data-${ directivePrefix }-navigation-id`;
- document.querySelectorAll( `[${ attrName }]` ).forEach( ( region ) => {
- const id = region.getAttribute( attrName );
- const fragment = getRegionRootFragment( region );
- render( page.regions[ id ], fragment );
- } );
- if ( page.title ) {
- document.title = page.title;
- }
-};
-
-// Variable to store the current navigation.
-let navigatingTo = '';
-
-/**
- * Navigates to the specified page.
- *
- * This function normalizes the passed href, fetchs the page HTML if needed, and
- * updates any interactive regions whose contents have changed. It also creates
- * a new entry in the browser session history.
- *
- * @param {string} href The page href.
- * @param {Object} [options] Options object.
- * @param {boolean} [options.force] If true, it forces re-fetching the URL.
- * @param {string} [options.html] HTML string to be used instead of fetching
- * the requested URL.
- * @param {boolean} [options.replace] If true, it replaces the current entry in
- * the browser session history.
- * @param {number} [options.timeout] Time until the navigation is aborted, in
- * milliseconds. Default is 10000.
- *
- * @return {Promise} Promise that resolves once the navigation is completed or
- * aborted.
- */
-export const navigate = async ( href, options = {} ) => {
- const url = cleanUrl( href );
- navigatingTo = href;
- prefetch( url, options );
-
- // Create a promise that resolves when the specified timeout ends. The
- // timeout value is 10 seconds by default.
- const timeoutPromise = new Promise( ( resolve ) =>
- setTimeout( resolve, options.timeout ?? 10000 )
- );
-
- const page = await Promise.race( [ pages.get( url ), timeoutPromise ] );
-
- // Once the page is fetched, the destination URL could have changed (e.g.,
- // by clicking another link in the meantime). If so, bail out, and let the
- // newer execution to update the HTML.
- if ( navigatingTo !== href ) return;
-
- if ( page ) {
- renderRegions( page );
- window.history[ options.replace ? 'replaceState' : 'pushState' ](
- {},
- '',
- href
- );
- } else {
- window.location.assign( href );
- await new Promise( () => {} );
- }
-};
-
-// Listen to the back and forward buttons and restore the page if it's in the
-// cache.
-window.addEventListener( 'popstate', async () => {
- const url = cleanUrl( window.location ); // Remove hash.
- const page = pages.has( url ) && ( await pages.get( url ) );
- if ( page ) {
- renderRegions( page );
- } else {
- window.location.reload();
- }
-} );
-
-// Initialize the router with the initial DOM.
-export const init = async () => {
- document
- .querySelectorAll( `[data-${ directivePrefix }-interactive]` )
- .forEach( ( node ) => {
- if ( ! hydratedIslands.has( node ) ) {
- const fragment = getRegionRootFragment( node );
- const vdom = toVdom( node );
- hydrate( vdom, fragment );
- }
- } );
-
- // Cache the current regions.
- pages.set(
- cleanUrl( window.location ),
- Promise.resolve( regionsToVdom( document ) )
- );
-};
diff --git a/packages/interactivity/src/store.ts b/packages/interactivity/src/store.ts
index 8463d1a0a51323..5177c72cfda462 100644
--- a/packages/interactivity/src/store.ts
+++ b/packages/interactivity/src/store.ts
@@ -36,12 +36,12 @@ const deepMerge = ( target: any, source: any ) => {
const parseInitialState = () => {
const storeTag = document.querySelector(
- `script[type="application/json"]#wp-interactivity-initial-state`
+ `script[type="application/json"]#wp-interactivity-data`
);
if ( ! storeTag?.textContent ) return {};
try {
- const initialState = JSON.parse( storeTag.textContent );
- if ( isObject( initialState ) ) return initialState;
+ const { state } = JSON.parse( storeTag.textContent );
+ if ( isObject( state ) ) return state;
throw Error( 'Parsed state is not an object' );
} catch ( e ) {
// eslint-disable-next-line no-console
diff --git a/packages/interactivity/src/vdom.js b/packages/interactivity/src/vdom.js
index 860a3149e6ffd6..4a7cfff9f9d0df 100644
--- a/packages/interactivity/src/vdom.js
+++ b/packages/interactivity/src/vdom.js
@@ -43,7 +43,7 @@ export function toVdom( root ) {
);
function walk( node ) {
- const { attributes, nodeType } = node;
+ const { attributes, nodeType, localName } = node;
if ( nodeType === 3 ) return [ node.data ];
if ( nodeType === 4 ) {
@@ -93,7 +93,7 @@ export function toVdom( root ) {
if ( ignore && ! island )
return [
- h( node.localName, {
+ h( localName, {
...props,
innerHTML: node.innerHTML,
__directives: { ignore: true },
@@ -118,20 +118,26 @@ export function toVdom( root ) {
);
}
- let child = treeWalker.firstChild();
- if ( child ) {
- while ( child ) {
- const [ vnode, nextChild ] = walk( child );
- if ( vnode ) children.push( vnode );
- child = nextChild || treeWalker.nextSibling();
+ if ( localName === 'template' ) {
+ props.content = [ ...node.content.childNodes ].map( ( childNode ) =>
+ toVdom( childNode )
+ );
+ } else {
+ let child = treeWalker.firstChild();
+ if ( child ) {
+ while ( child ) {
+ const [ vnode, nextChild ] = walk( child );
+ if ( vnode ) children.push( vnode );
+ child = nextChild || treeWalker.nextSibling();
+ }
+ treeWalker.parentNode();
}
- treeWalker.parentNode();
}
// Restore previous namespace.
if ( island ) namespaces.pop();
- return [ h( node.localName, props, children ) ];
+ return [ h( localName, props, children ) ];
}
return walk( treeWalker.currentNode );
diff --git a/packages/interface/CHANGELOG.md b/packages/interface/CHANGELOG.md
index a684dc5a6a4eff..076b8e7ef5a279 100644
--- a/packages/interface/CHANGELOG.md
+++ b/packages/interface/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 5.27.0 (2024-01-24)
+
## 5.26.0 (2024-01-10)
## 5.25.0 (2023-12-13)
diff --git a/packages/interface/package.json b/packages/interface/package.json
index feebc542d3a781..abf9b5c4ae66cf 100644
--- a/packages/interface/package.json
+++ b/packages/interface/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/interface",
- "version": "5.26.0",
+ "version": "5.27.0",
"description": "Interface module for WordPress. The package contains shared functionality across the modern JavaScript-based WordPress screens.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/is-shallow-equal/CHANGELOG.md b/packages/is-shallow-equal/CHANGELOG.md
index d7d7028dd5e40a..1df74538788be8 100644
--- a/packages/is-shallow-equal/CHANGELOG.md
+++ b/packages/is-shallow-equal/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 4.50.0 (2024-01-24)
+
## 4.49.0 (2024-01-10)
## 4.48.0 (2023-12-13)
diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json
index f2600696d54d67..cd1de378fcaa8c 100644
--- a/packages/is-shallow-equal/package.json
+++ b/packages/is-shallow-equal/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/is-shallow-equal",
- "version": "4.49.0",
+ "version": "4.50.0",
"description": "Test for shallow equality between two objects or arrays.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/jest-console/CHANGELOG.md b/packages/jest-console/CHANGELOG.md
index f82d95dc08cd44..e894a4d5b2a7bc 100644
--- a/packages/jest-console/CHANGELOG.md
+++ b/packages/jest-console/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 7.21.0 (2024-01-24)
+
## 7.20.0 (2024-01-10)
## 7.19.0 (2023-12-13)
diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json
index 4dfdea8d61175a..18654b4d1e463c 100644
--- a/packages/jest-console/package.json
+++ b/packages/jest-console/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/jest-console",
- "version": "7.20.0",
+ "version": "7.21.0",
"description": "Custom Jest matchers for the Console object.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/jest-preset-default/CHANGELOG.md b/packages/jest-preset-default/CHANGELOG.md
index d0525fe187ad56..7d0fb49595b40e 100644
--- a/packages/jest-preset-default/CHANGELOG.md
+++ b/packages/jest-preset-default/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 11.21.0 (2024-01-24)
+
## 11.20.0 (2024-01-10)
## 11.19.0 (2023-12-13)
diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json
index aa787861ed89d9..6b665282e6e9b0 100644
--- a/packages/jest-preset-default/package.json
+++ b/packages/jest-preset-default/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/jest-preset-default",
- "version": "11.20.0",
+ "version": "11.21.0",
"description": "Default Jest preset for WordPress development.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/jest-puppeteer-axe/CHANGELOG.md b/packages/jest-puppeteer-axe/CHANGELOG.md
index 89acfcecff4da6..dffe7cdad5d6a9 100644
--- a/packages/jest-puppeteer-axe/CHANGELOG.md
+++ b/packages/jest-puppeteer-axe/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 6.21.0 (2024-01-24)
+
## 6.20.0 (2024-01-10)
## 6.19.0 (2023-12-13)
diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json
index d30e2ea7812862..db88d8f6022ef1 100644
--- a/packages/jest-puppeteer-axe/package.json
+++ b/packages/jest-puppeteer-axe/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/jest-puppeteer-axe",
- "version": "6.20.0",
+ "version": "6.21.0",
"description": "Axe API integration with Jest and Puppeteer.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/keyboard-shortcuts/CHANGELOG.md b/packages/keyboard-shortcuts/CHANGELOG.md
index 2af0ab1eb2905a..671a8c698d371f 100644
--- a/packages/keyboard-shortcuts/CHANGELOG.md
+++ b/packages/keyboard-shortcuts/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 4.27.0 (2024-01-24)
+
## 4.26.0 (2024-01-10)
## 4.25.0 (2023-12-13)
diff --git a/packages/keyboard-shortcuts/package.json b/packages/keyboard-shortcuts/package.json
index 7f336adb525102..e9e26283fd316b 100644
--- a/packages/keyboard-shortcuts/package.json
+++ b/packages/keyboard-shortcuts/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/keyboard-shortcuts",
- "version": "4.26.0",
+ "version": "4.27.0",
"description": "Handling keyboard shortcuts.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/keycodes/CHANGELOG.md b/packages/keycodes/CHANGELOG.md
index 6fda0148feaa8a..a7a669a6f853d2 100644
--- a/packages/keycodes/CHANGELOG.md
+++ b/packages/keycodes/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 3.50.0 (2024-01-24)
+
## 3.49.0 (2024-01-10)
## 3.48.0 (2023-12-13)
diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json
index c5ce48ea8c2c30..61067afead5858 100644
--- a/packages/keycodes/package.json
+++ b/packages/keycodes/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/keycodes",
- "version": "3.49.0",
+ "version": "3.50.0",
"description": "Keycodes utilities for WordPress. Used to check for keyboard events across browsers/operating systems.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/lazy-import/CHANGELOG.md b/packages/lazy-import/CHANGELOG.md
index 214141e0d476d4..211f48839a2e88 100644
--- a/packages/lazy-import/CHANGELOG.md
+++ b/packages/lazy-import/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 1.37.0 (2024-01-24)
+
## 1.36.0 (2024-01-10)
## 1.35.0 (2023-12-13)
diff --git a/packages/lazy-import/package.json b/packages/lazy-import/package.json
index 8ad948e62408aa..2a62bd842f48f9 100644
--- a/packages/lazy-import/package.json
+++ b/packages/lazy-import/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/lazy-import",
- "version": "1.36.0",
+ "version": "1.37.0",
"description": "Lazily import a module, installing it automatically if missing.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/list-reusable-blocks/CHANGELOG.md b/packages/list-reusable-blocks/CHANGELOG.md
index 4c153c4539b359..5e578c0fe5af17 100644
--- a/packages/list-reusable-blocks/CHANGELOG.md
+++ b/packages/list-reusable-blocks/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 4.27.0 (2024-01-24)
+
## 4.26.0 (2024-01-10)
## 4.25.0 (2023-12-13)
diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json
index 403809ae7d72a0..04a19d1f6b1ddf 100644
--- a/packages/list-reusable-blocks/package.json
+++ b/packages/list-reusable-blocks/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/list-reusable-blocks",
- "version": "4.26.0",
+ "version": "4.27.0",
"description": "Adding Export/Import support to the reusable blocks listing.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/media-utils/CHANGELOG.md b/packages/media-utils/CHANGELOG.md
index d7a8c61e149bf1..277698bd365337 100644
--- a/packages/media-utils/CHANGELOG.md
+++ b/packages/media-utils/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 4.41.0 (2024-01-24)
+
## 4.40.0 (2024-01-10)
## 4.39.0 (2023-12-13)
diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json
index b01403efff06ed..a74cadcab596f5 100644
--- a/packages/media-utils/package.json
+++ b/packages/media-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/media-utils",
- "version": "4.40.0",
+ "version": "4.41.0",
"description": "WordPress Media Upload Utils.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/notices/CHANGELOG.md b/packages/notices/CHANGELOG.md
index 03f5931c9899b9..f58afb038780ca 100644
--- a/packages/notices/CHANGELOG.md
+++ b/packages/notices/CHANGELOG.md
@@ -2,6 +2,8 @@
## Unreleased
+## 4.18.0 (2024-01-24)
+
## 4.17.0 (2024-01-10)
## 4.16.0 (2023-12-13)
diff --git a/packages/notices/package.json b/packages/notices/package.json
index 4f763cb6ed5766..6d456aba34328e 100644
--- a/packages/notices/package.json
+++ b/packages/notices/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/notices",
- "version": "4.17.0",
+ "version": "4.18.0",
"description": "State management for notices.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/notices/src/store/actions.js b/packages/notices/src/store/actions.js
index 445eed10650b61..fb1c4243073511 100644
--- a/packages/notices/src/store/actions.js
+++ b/packages/notices/src/store/actions.js
@@ -330,7 +330,7 @@ export function removeNotice( id, context = DEFAULT_CONTEXT ) {
* const notices = useSelect( ( select ) =>
* select( noticesStore ).getNotices()
* );
- * const { removeNotices } = useDispatch( noticesStore );
+ * const { removeAllNotices } = useDispatch( noticesStore );
* return (
* <>
*
', $tags->get_inner_html() );
- }
-
- public function test_set_inner_html_on_void_element_has_no_effect() {
- $tags = new WP_Directive_Processor( self::HTML );
-
- $tags->next_tag( 'img' );
- $content = $tags->set_inner_html( 'This is the new img content' );
- $this->assertFalse( $content );
- $this->assertSame( self::HTML, $tags->get_updated_html() );
- }
-
- public function test_set_inner_html_sets_content_correctly() {
- $tags = new WP_Directive_Processor( self::HTML );
-
- $tags->next_tag( 'section' );
- $tags->set_inner_html( 'This is the new section content.' );
- $this->assertSame( '
outside
This is the new section content.', $tags->get_updated_html() );
- }
-
- public function test_set_inner_html_updates_bookmarks_correctly() {
- $tags = new WP_Directive_Processor( self::HTML );
-
- $tags->next_tag( 'div' );
- $tags->set_bookmark( 'start' );
- $tags->next_tag( 'img' );
- $this->assertSame( 'IMG', $tags->get_tag() );
- $tags->set_bookmark( 'after' );
- $tags->seek( 'start' );
-
- $tags->set_inner_html( 'This is the new div content.' );
- $this->assertSame( '
This is the new div content.
inside
', $tags->get_updated_html() );
- $tags->seek( 'after' );
- $this->assertSame( 'IMG', $tags->get_tag() );
- }
-
- public function test_set_inner_html_subsequent_updates_on_the_same_tag_work() {
- $tags = new WP_Directive_Processor( self::HTML );
-
- $tags->next_tag( 'section' );
- $tags->set_inner_html( 'This is the new section content.' );
- $tags->set_inner_html( 'This is the even newer section content.' );
- $this->assertSame( '
outside
This is the even newer section content.', $tags->get_updated_html() );
- }
-
- public function test_set_inner_html_followed_by_set_attribute_works() {
- $tags = new WP_Directive_Processor( self::HTML );
-
- $tags->next_tag( 'section' );
- $tags->set_inner_html( 'This is the new section content.' );
- $tags->set_attribute( 'id', 'thesection' );
- $this->assertSame( '
outside
This is the new section content.', $tags->get_updated_html() );
- }
-
- public function test_set_inner_html_preceded_by_set_attribute_works() {
- $tags = new WP_Directive_Processor( self::HTML );
-
- $tags->next_tag( 'section' );
- $tags->set_attribute( 'id', 'thesection' );
- $tags->set_inner_html( 'This is the new section content.' );
- $this->assertSame( '
outside
This is the new section content.', $tags->get_updated_html() );
- }
-
- /**
- * TODO: Review this, how that the code is in Gutenberg.
- */
- public function test_set_inner_html_invalidates_bookmarks_that_point_to_replaced_content() {
- $this->markTestSkipped( "This requires on bookmark invalidation, which is only in GB's WP 6.3 compat layer." );
-
- $tags = new WP_Directive_Processor( self::HTML );
-
- $tags->next_tag( 'section' );
- $tags->set_bookmark( 'start' );
- $tags->next_tag( 'img' );
- $tags->set_bookmark( 'replaced' );
- $tags->seek( 'start' );
-
- $tags->set_inner_html( 'This is the new section content.' );
- $this->assertSame( '