From b372527409c9994f583536828218770cc0cc8b29 Mon Sep 17 00:00:00 2001 From: dhruvang21 <105810308+dhruvang21@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:26:47 +0530 Subject: [PATCH 01/30] Remove hasArrowIndicator prop (#66204) Co-authored-by: dhruvang21 Co-authored-by: t-hamano --- packages/block-library/src/table/edit.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 95162f4e14c00b..f1cb3fa5d8b8ae 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -465,7 +465,6 @@ function TableEdit( { Date: Thu, 17 Oct 2024 13:57:43 +0100 Subject: [PATCH 02/30] Site Editor: Prepare route registration by refactoring the site editor router (#66030) Co-authored-by: youknowriad Co-authored-by: ramonjd Co-authored-by: jsnajdr Co-authored-by: mcsf Co-authored-by: kevin940726 --- .../edit-site/src/components/app/index.js | 6 +- .../edit-site/src/components/layout/index.js | 4 +- .../edit-site/src/components/layout/router.js | 151 +++--------------- .../src/components/posts-app/index.js | 4 +- .../src/components/posts-app/router.js | 6 +- .../site-editor-routes/home-edit.js | 17 ++ .../site-editor-routes/home-view.js | 16 ++ .../components/site-editor-routes/index.js | 60 +++++++ .../site-editor-routes/navigation-edit.js | 22 +++ .../navigation-item-edit.js | 26 +++ .../navigation-item-view.js | 25 +++ .../site-editor-routes/navigation-view.js | 21 +++ .../site-editor-routes/pages-edit.js | 35 ++++ .../pages-list-view-quick-edit.js | 56 +++++++ .../site-editor-routes/pages-list-view.js | 44 +++++ .../pages-view-quick-edit.js | 53 ++++++ .../site-editor-routes/pages-view.js | 39 +++++ .../site-editor-routes/patterns-edit.js | 24 +++ .../site-editor-routes/patterns-view.js | 22 +++ .../site-editor-routes/styles-edit.js | 17 ++ .../site-editor-routes/styles-view.js | 16 ++ .../site-editor-routes/templates-edit.js | 22 +++ .../site-editor-routes/templates-list-view.js | 28 ++++ .../site-editor-routes/templates-view.js | 22 +++ .../edit-site/src/store/private-actions.js | 7 + .../edit-site/src/store/private-selectors.js | 4 + packages/edit-site/src/store/reducer.js | 10 ++ 27 files changed, 615 insertions(+), 142 deletions(-) create mode 100644 packages/edit-site/src/components/site-editor-routes/home-edit.js create mode 100644 packages/edit-site/src/components/site-editor-routes/home-view.js create mode 100644 packages/edit-site/src/components/site-editor-routes/index.js create mode 100644 packages/edit-site/src/components/site-editor-routes/navigation-edit.js create mode 100644 packages/edit-site/src/components/site-editor-routes/navigation-item-edit.js create mode 100644 packages/edit-site/src/components/site-editor-routes/navigation-item-view.js create mode 100644 packages/edit-site/src/components/site-editor-routes/navigation-view.js create mode 100644 packages/edit-site/src/components/site-editor-routes/pages-edit.js create mode 100644 packages/edit-site/src/components/site-editor-routes/pages-list-view-quick-edit.js create mode 100644 packages/edit-site/src/components/site-editor-routes/pages-list-view.js create mode 100644 packages/edit-site/src/components/site-editor-routes/pages-view-quick-edit.js create mode 100644 packages/edit-site/src/components/site-editor-routes/pages-view.js create mode 100644 packages/edit-site/src/components/site-editor-routes/patterns-edit.js create mode 100644 packages/edit-site/src/components/site-editor-routes/patterns-view.js create mode 100644 packages/edit-site/src/components/site-editor-routes/styles-edit.js create mode 100644 packages/edit-site/src/components/site-editor-routes/styles-view.js create mode 100644 packages/edit-site/src/components/site-editor-routes/templates-edit.js create mode 100644 packages/edit-site/src/components/site-editor-routes/templates-list-view.js create mode 100644 packages/edit-site/src/components/site-editor-routes/templates-view.js diff --git a/packages/edit-site/src/components/app/index.js b/packages/edit-site/src/components/app/index.js index 452c36014f5db9..133a376c9c246d 100644 --- a/packages/edit-site/src/components/app/index.js +++ b/packages/edit-site/src/components/app/index.js @@ -20,8 +20,9 @@ import { unlock } from '../../lock-unlock'; import { useCommonCommands } from '../../hooks/commands/use-common-commands'; import { useEditModeCommands } from '../../hooks/commands/use-edit-mode-commands'; import useInitEditedEntityFromURL from '../sync-state-with-url/use-init-edited-entity-from-url'; -import useLayoutAreas from '../layout/router'; +import useActiveRoute from '../layout/router'; import useSetCommandContext from '../../hooks/commands/use-set-command-context'; +import { useRegisterSiteEditorRoutes } from '../site-editor-routes'; const { RouterProvider } = unlock( routerPrivateApis ); const { GlobalStylesProvider } = unlock( editorPrivateApis ); @@ -32,7 +33,8 @@ function AppLayout() { useEditModeCommands(); useCommonCommands(); useSetCommandContext(); - const route = useLayoutAreas(); + useRegisterSiteEditorRoutes(); + const route = useActiveRoute(); return ; } diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 2619f7c96dcf74..d02f2905f24d85 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -68,9 +68,9 @@ export default function Layout( { route } ) { const isEditorLoading = useIsSiteEditorLoading(); const [ isResizableFrameOversized, setIsResizableFrameOversized ] = useState( false ); - const { key: routeKey, areas, widths } = route; + const { name: routeKey, areas, widths } = route; const animationRef = useMovingAnimation( { - triggerAnimationOnChange: canvasMode + '__' + routeKey, + triggerAnimationOnChange: canvasMode, } ); const [ backgroundColor ] = useGlobalStyle( 'color.background' ); diff --git a/packages/edit-site/src/components/layout/router.js b/packages/edit-site/src/components/layout/router.js index 3fd0cc560d9433..912a837555e0d1 100644 --- a/packages/edit-site/src/components/layout/router.js +++ b/packages/edit-site/src/components/layout/router.js @@ -2,31 +2,19 @@ * WordPress dependencies */ import { privateApis as routerPrivateApis } from '@wordpress/router'; -import { __ } from '@wordpress/i18n'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useMemo } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ import { unlock } from '../../lock-unlock'; -import Editor from '../editor'; -import PostList from '../post-list'; -import PagePatterns from '../page-patterns'; -import PageTemplates from '../page-templates'; -import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; -import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; -import SidebarNavigationScreenNavigationMenus from '../sidebar-navigation-screen-navigation-menus'; -import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; -import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; -import SidebarNavigationScreenNavigationMenu from '../sidebar-navigation-screen-navigation-menu'; -import DataViewsSidebarContent from '../sidebar-dataviews'; import { NAVIGATION_POST_TYPE, PATTERN_TYPES, TEMPLATE_PART_POST_TYPE, TEMPLATE_POST_TYPE, } from '../../utils/constants'; -import { PostEdit } from '../post-edit'; +import { store as editSiteStore } from '../../store'; const { useLocation, useHistory } = unlock( routerPrivateApis ); @@ -73,129 +61,26 @@ function useRedirectOldPaths() { }, [ history, params ] ); } -export default function useLayoutAreas() { +export default function useActiveRoute() { const { params } = useLocation(); - const { postType, postId, path, layout, isCustom, canvas, quickEdit } = - params; - const hasEditCanvasMode = canvas === 'edit'; useRedirectOldPaths(); - - // Page list - if ( postType === 'page' ) { - const isListLayout = layout === 'list' || ! layout; - const showQuickEdit = quickEdit && ! isListLayout; - return { - key: 'pages', - areas: { - sidebar: ( - } - /> - ), - content: , - preview: ! showQuickEdit && - ( isListLayout || hasEditCanvasMode ) && , - mobile: hasEditCanvasMode ? ( - - ) : ( - - ), - edit: showQuickEdit && ( - - ), - }, - widths: { - content: isListLayout ? 380 : undefined, - edit: showQuickEdit ? 380 : undefined, - }, - }; - } - - // Templates - if ( postType === TEMPLATE_POST_TYPE ) { - const isListLayout = isCustom !== 'true' && layout === 'list'; - return { - key: 'templates', - areas: { - sidebar: ( - - ), - content: , - preview: ( isListLayout || hasEditCanvasMode ) && , - mobile: hasEditCanvasMode ? : , - }, - widths: { - content: isListLayout ? 380 : undefined, - }, - }; - } - - // Patterns - if ( - [ TEMPLATE_PART_POST_TYPE, PATTERN_TYPES.user ].includes( postType ) - ) { - return { - key: 'patterns', - areas: { - sidebar: , - content: , - mobile: hasEditCanvasMode ? : , - preview: hasEditCanvasMode && , - }, - }; - } - - // Styles - if ( path === '/wp_global_styles' ) { - return { - key: 'styles', - areas: { - sidebar: ( - - ), - preview: , - mobile: hasEditCanvasMode && , - }, - }; - } - - // Navigation - if ( postType === NAVIGATION_POST_TYPE ) { - if ( postId ) { + const routes = useSelect( ( select ) => { + return unlock( select( editSiteStore ) ).getRoutes(); + }, [] ); + return useMemo( () => { + const matchedRoute = routes.find( ( route ) => route.match( params ) ); + if ( ! matchedRoute ) { return { - key: 'navigation', - areas: { - sidebar: ( - - ), - preview: , - mobile: hasEditCanvasMode && , - }, + key: 404, + areas: {}, + widths: {}, }; } + return { - key: 'navigation', - areas: { - sidebar: ( - - ), - preview: , - mobile: hasEditCanvasMode && , - }, + name: matchedRoute.name, + areas: matchedRoute.areas, + widths: matchedRoute.widths, }; - } - - // Fallback shows the home page preview - return { - key: 'default', - areas: { - sidebar: , - preview: , - mobile: hasEditCanvasMode && , - }, - }; + }, [ routes, params ] ); } diff --git a/packages/edit-site/src/components/posts-app/index.js b/packages/edit-site/src/components/posts-app/index.js index 12bdf33711031c..8b5377bb2e65bd 100644 --- a/packages/edit-site/src/components/posts-app/index.js +++ b/packages/edit-site/src/components/posts-app/index.js @@ -12,7 +12,7 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; */ import useInitEditedEntityFromURL from '../sync-state-with-url/use-init-edited-entity-from-url'; import Layout from '../layout'; -import useLayoutAreas from './router'; +import useActiveRoute from './router'; import { unlock } from '../../lock-unlock'; const { RouterProvider } = unlock( routerPrivateApis ); @@ -21,7 +21,7 @@ const { GlobalStylesProvider } = unlock( editorPrivateApis ); function PostsLayout() { // This ensures the edited entity id and type are initialized properly. useInitEditedEntityFromURL(); - const route = useLayoutAreas(); + const route = useActiveRoute(); return ; } diff --git a/packages/edit-site/src/components/posts-app/router.js b/packages/edit-site/src/components/posts-app/router.js index 5ffbe1831f2a24..de89567b262094 100644 --- a/packages/edit-site/src/components/posts-app/router.js +++ b/packages/edit-site/src/components/posts-app/router.js @@ -17,7 +17,7 @@ import PostList from '../post-list'; const { useLocation } = unlock( routerPrivateApis ); -export default function useLayoutAreas() { +export default function useActiveRoute() { const { params = {} } = useLocation(); const { postType, layout, canvas } = params; const labels = useSelect( @@ -31,7 +31,7 @@ export default function useLayoutAreas() { if ( [ 'post' ].includes( postType ) ) { const isListLayout = layout === 'list' || ! layout; return { - key: 'posts-list', + name: 'posts-list', areas: { sidebar: ( , preview: , diff --git a/packages/edit-site/src/components/site-editor-routes/home-edit.js b/packages/edit-site/src/components/site-editor-routes/home-edit.js new file mode 100644 index 00000000000000..f6e6499254082f --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/home-edit.js @@ -0,0 +1,17 @@ +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; + +export const homeEditRoute = { + name: 'home-edit', + match: ( params ) => { + return params.canvas === 'edit'; + }, + areas: { + sidebar: , + preview: , + mobile: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/home-view.js b/packages/edit-site/src/components/site-editor-routes/home-view.js new file mode 100644 index 00000000000000..63d3d021e82083 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/home-view.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreenMain from '../sidebar-navigation-screen-main'; + +export const homeViewRoute = { + name: 'home-view', + match: ( params ) => { + return params.canvas !== 'edit'; + }, + areas: { + sidebar: , + preview: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/index.js b/packages/edit-site/src/components/site-editor-routes/index.js new file mode 100644 index 00000000000000..ddc37b353885c1 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/index.js @@ -0,0 +1,60 @@ +/** + * WordPress dependencies + */ +import { useRegistry, useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; +import { store as siteEditorStore } from '../../store'; +import { homeViewRoute } from './home-view'; +import { homeEditRoute } from './home-edit'; +import { navigationViewRoute } from './navigation-view'; +import { navigationEditRoute } from './navigation-edit'; +import { navigationItemEditRoute } from './navigation-item-edit'; +import { navigationItemViewRoute } from './navigation-item-view'; +import { stylesEditRoute } from './styles-edit'; +import { stylesViewRoute } from './styles-view'; +import { patternsEditRoute } from './patterns-edit'; +import { patternsViewRoute } from './patterns-view'; +import { templatesEditRoute } from './templates-edit'; +import { templatesListViewRoute } from './templates-list-view'; +import { templatesViewRoute } from './templates-view'; +import { pagesViewRoute } from './pages-view'; +import { pagesEditRoute } from './pages-edit'; +import { pagesListViewRoute } from './pages-list-view'; +import { pagesListViewQuickEditRoute } from './pages-list-view-quick-edit'; +import { pagesViewQuickEditRoute } from './pages-view-quick-edit'; + +const routes = [ + pagesListViewQuickEditRoute, + pagesListViewRoute, + pagesViewQuickEditRoute, + pagesViewRoute, + pagesEditRoute, + templatesEditRoute, + templatesListViewRoute, + templatesViewRoute, + patternsViewRoute, + patternsEditRoute, + stylesViewRoute, + stylesEditRoute, + navigationItemViewRoute, + navigationItemEditRoute, + navigationViewRoute, + navigationEditRoute, + homeViewRoute, + homeEditRoute, +]; + +export function useRegisterSiteEditorRoutes() { + const registry = useRegistry(); + const { registerRoute } = unlock( useDispatch( siteEditorStore ) ); + useEffect( () => { + registry.batch( () => { + routes.forEach( registerRoute ); + } ); + }, [ registry, registerRoute ] ); +} diff --git a/packages/edit-site/src/components/site-editor-routes/navigation-edit.js b/packages/edit-site/src/components/site-editor-routes/navigation-edit.js new file mode 100644 index 00000000000000..fdba963c41d0cb --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/navigation-edit.js @@ -0,0 +1,22 @@ +/** + * Internal dependencies + */ +import { NAVIGATION_POST_TYPE } from '../../utils/constants'; +import Editor from '../editor'; +import SidebarNavigationScreenNavigationMenus from '../sidebar-navigation-screen-navigation-menus'; + +export const navigationEditRoute = { + name: 'navigation-edit', + match: ( params ) => { + return ( + params.postType === NAVIGATION_POST_TYPE && + ! params.postId && + params.canvas === 'edit' + ); + }, + areas: { + sidebar: , + preview: , + mobile: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/navigation-item-edit.js b/packages/edit-site/src/components/site-editor-routes/navigation-item-edit.js new file mode 100644 index 00000000000000..b03cdbd995ac7c --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/navigation-item-edit.js @@ -0,0 +1,26 @@ +/** + * Internal dependencies + */ +import { NAVIGATION_POST_TYPE } from '../../utils/constants'; +import Editor from '../editor'; +import SidebarNavigationScreenNavigationMenu from '../sidebar-navigation-screen-navigation-menu'; + +export const navigationItemEditRoute = { + name: 'navigation-item-edit', + match: ( params ) => { + return ( + params.postType === NAVIGATION_POST_TYPE && + !! params.postId && + params.canvas === 'edit' + ); + }, + areas: { + sidebar: ( + + ), + preview: , + mobile: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/navigation-item-view.js b/packages/edit-site/src/components/site-editor-routes/navigation-item-view.js new file mode 100644 index 00000000000000..d04a03a8f9df38 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/navigation-item-view.js @@ -0,0 +1,25 @@ +/** + * Internal dependencies + */ +import { NAVIGATION_POST_TYPE } from '../../utils/constants'; +import Editor from '../editor'; +import SidebarNavigationScreenNavigationMenu from '../sidebar-navigation-screen-navigation-menu'; + +export const navigationItemViewRoute = { + name: 'navigation-item-view', + match: ( params ) => { + return ( + params.postType === NAVIGATION_POST_TYPE && + !! params.postId && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: ( + + ), + preview: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/navigation-view.js b/packages/edit-site/src/components/site-editor-routes/navigation-view.js new file mode 100644 index 00000000000000..59c38a2f1d099a --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/navigation-view.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies + */ +import { NAVIGATION_POST_TYPE } from '../../utils/constants'; +import Editor from '../editor'; +import SidebarNavigationScreenNavigationMenus from '../sidebar-navigation-screen-navigation-menus'; + +export const navigationViewRoute = { + name: 'navigation-view', + match: ( params ) => { + return ( + params.postType === NAVIGATION_POST_TYPE && + ! params.postId && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: , + preview: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/pages-edit.js b/packages/edit-site/src/components/site-editor-routes/pages-edit.js new file mode 100644 index 00000000000000..ef4c7efbfb09c2 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/pages-edit.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import PostList from '../post-list'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import Editor from '../editor'; + +function PageList() { + return ; +} + +export const pagesEditRoute = { + name: 'pages-edit', + match: ( params ) => { + return params.postType === 'page' && params.canvas === 'edit'; + }, + areas: { + sidebar: ( + } + /> + ), + content: , + mobile: , + preview: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/pages-list-view-quick-edit.js b/packages/edit-site/src/components/site-editor-routes/pages-list-view-quick-edit.js new file mode 100644 index 00000000000000..9eb33e05a99bb0 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/pages-list-view-quick-edit.js @@ -0,0 +1,56 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import PostList from '../post-list'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import { unlock } from '../../lock-unlock'; +import { PostEdit } from '../post-edit'; +import Editor from '../editor'; + +const { useLocation } = unlock( routerPrivateApis ); + +function PageList() { + return ; +} + +function PageQuickEdit() { + const { params } = useLocation(); + return ; +} + +export const pagesListViewQuickEditRoute = { + name: 'pages-list-view-quick-edit', + match: ( params ) => { + return ( + params.isCustom !== 'true' && + ( params.layout ?? 'list' ) === 'list' && + !! params.quickEdit && + params.postType === 'page' && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: ( + } + /> + ), + content: , + mobile: , + preview: , + edit: , + }, + widths: { + content: 380, + edit: 380, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/pages-list-view.js b/packages/edit-site/src/components/site-editor-routes/pages-list-view.js new file mode 100644 index 00000000000000..74b39848e83f2b --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/pages-list-view.js @@ -0,0 +1,44 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import PostList from '../post-list'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import Editor from '../editor'; + +function PageList() { + return ; +} + +export const pagesListViewRoute = { + name: 'pages-list-view', + match: ( params ) => { + return ( + params.isCustom !== 'true' && + ( params.layout ?? 'list' ) === 'list' && + ! params.quickEdit && + params.postType === 'page' && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: ( + } + /> + ), + content: , + preview: , + mobile: , + }, + widths: { + content: 380, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/pages-view-quick-edit.js b/packages/edit-site/src/components/site-editor-routes/pages-view-quick-edit.js new file mode 100644 index 00000000000000..907054364c8a93 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/pages-view-quick-edit.js @@ -0,0 +1,53 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import PostList from '../post-list'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import { unlock } from '../../lock-unlock'; +import { PostEdit } from '../post-edit'; + +const { useLocation } = unlock( routerPrivateApis ); + +function PageList() { + return ; +} + +function PageQuickEdit() { + const { params } = useLocation(); + return ; +} + +export const pagesViewQuickEditRoute = { + name: 'pages-view-quick-edit', + match: ( params ) => { + return ( + ( params.isCustom === 'true' || + ( params.layout ?? 'list' ) !== 'list' ) && + !! params.quickEdit && + params.postType === 'page' && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: ( + } + /> + ), + content: , + mobile: , + edit: , + }, + widths: { + edit: 380, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/pages-view.js b/packages/edit-site/src/components/site-editor-routes/pages-view.js new file mode 100644 index 00000000000000..df7e211022cacf --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/pages-view.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import PostList from '../post-list'; +import DataViewsSidebarContent from '../sidebar-dataviews'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; + +function PageList() { + return ; +} + +export const pagesViewRoute = { + name: 'pages-view', + match: ( params ) => { + return ( + ( params.isCustom === 'true' || + ( params.layout ?? 'list' ) !== 'list' ) && + ! params.quickEdit && + params.postType === 'page' && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: ( + } + /> + ), + content: , + mobile: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/patterns-edit.js b/packages/edit-site/src/components/site-editor-routes/patterns-edit.js new file mode 100644 index 00000000000000..eaf1fd68020181 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/patterns-edit.js @@ -0,0 +1,24 @@ +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; +import PagePatterns from '../page-patterns'; +import { PATTERN_TYPES, TEMPLATE_PART_POST_TYPE } from '../../utils/constants'; + +export const patternsEditRoute = { + name: 'patterns-edit', + match: ( params ) => { + return ( + [ TEMPLATE_PART_POST_TYPE, PATTERN_TYPES.user ].includes( + params.postType + ) && params.canvas === 'edit' + ); + }, + areas: { + sidebar: , + content: , + mobile: , + preview: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/patterns-view.js b/packages/edit-site/src/components/site-editor-routes/patterns-view.js new file mode 100644 index 00000000000000..468f7f14abc139 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/patterns-view.js @@ -0,0 +1,22 @@ +/** + * Internal dependencies + */ +import SidebarNavigationScreenPatterns from '../sidebar-navigation-screen-patterns'; +import PagePatterns from '../page-patterns'; +import { PATTERN_TYPES, TEMPLATE_PART_POST_TYPE } from '../../utils/constants'; + +export const patternsViewRoute = { + name: 'patterns-view', + match: ( params ) => { + return ( + [ TEMPLATE_PART_POST_TYPE, PATTERN_TYPES.user ].includes( + params.postType + ) && params.canvas !== 'edit' + ); + }, + areas: { + sidebar: , + content: , + mobile: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/styles-edit.js b/packages/edit-site/src/components/site-editor-routes/styles-edit.js new file mode 100644 index 00000000000000..ff52b957bc3609 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/styles-edit.js @@ -0,0 +1,17 @@ +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; + +export const stylesEditRoute = { + name: 'styles-edit', + match: ( params ) => { + return params.path === '/wp_global_styles' && params.canvas === 'edit'; + }, + areas: { + sidebar: , + preview: , + mobile: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/styles-view.js b/packages/edit-site/src/components/site-editor-routes/styles-view.js new file mode 100644 index 00000000000000..856a610eb23677 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/styles-view.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +import Editor from '../editor'; +import SidebarNavigationScreenGlobalStyles from '../sidebar-navigation-screen-global-styles'; + +export const stylesViewRoute = { + name: 'styles-view', + match: ( params ) => { + return params.path === '/wp_global_styles' && params.canvas !== 'edit'; + }, + areas: { + sidebar: , + preview: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/templates-edit.js b/packages/edit-site/src/components/site-editor-routes/templates-edit.js new file mode 100644 index 00000000000000..488e9decc1888c --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/templates-edit.js @@ -0,0 +1,22 @@ +/** + * Internal dependencies + */ +import { TEMPLATE_POST_TYPE } from '../../utils/constants'; +import PageTemplates from '../page-templates'; +import Editor from '../editor'; +import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; + +export const templatesEditRoute = { + name: 'templates-edit', + match: ( params ) => { + return ( + params.postType === TEMPLATE_POST_TYPE && params.canvas === 'edit' + ); + }, + areas: { + sidebar: , + content: , + mobile: , + preview: , + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/templates-list-view.js b/packages/edit-site/src/components/site-editor-routes/templates-list-view.js new file mode 100644 index 00000000000000..7cdda1b13c0b47 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/templates-list-view.js @@ -0,0 +1,28 @@ +/** + * Internal dependencies + */ +import { TEMPLATE_POST_TYPE } from '../../utils/constants'; +import PageTemplates from '../page-templates'; +import Editor from '../editor'; +import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; + +export const templatesListViewRoute = { + name: 'templates-list-view', + match: ( params ) => { + return ( + params.isCustom !== 'true' && + params.layout === 'list' && + params.postType === TEMPLATE_POST_TYPE && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: , + content: , + mobile: , + preview: , + }, + widths: { + content: 380, + }, +}; diff --git a/packages/edit-site/src/components/site-editor-routes/templates-view.js b/packages/edit-site/src/components/site-editor-routes/templates-view.js new file mode 100644 index 00000000000000..40fd88c0e60a61 --- /dev/null +++ b/packages/edit-site/src/components/site-editor-routes/templates-view.js @@ -0,0 +1,22 @@ +/** + * Internal dependencies + */ +import { TEMPLATE_POST_TYPE } from '../../utils/constants'; +import PageTemplates from '../page-templates'; +import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen-templates-browse'; + +export const templatesViewRoute = { + name: 'templates-view', + match: ( params ) => { + return ( + ( params.isCustom === 'true' || params.layout !== 'list' ) && + params.postType === TEMPLATE_POST_TYPE && + params.canvas !== 'edit' + ); + }, + areas: { + sidebar: , + content: , + mobile: , + }, +}; diff --git a/packages/edit-site/src/store/private-actions.js b/packages/edit-site/src/store/private-actions.js index 94bcc490b6fd60..1c5924a292765b 100644 --- a/packages/edit-site/src/store/private-actions.js +++ b/packages/edit-site/src/store/private-actions.js @@ -90,3 +90,10 @@ export const setEditorCanvasContainerView = view, } ); }; + +export function registerRoute( route ) { + return { + type: 'REGISTER_ROUTE', + route, + }; +} diff --git a/packages/edit-site/src/store/private-selectors.js b/packages/edit-site/src/store/private-selectors.js index 1f1f6e999fdb29..d0f1d3f4196008 100644 --- a/packages/edit-site/src/store/private-selectors.js +++ b/packages/edit-site/src/store/private-selectors.js @@ -19,3 +19,7 @@ export function getCanvasMode( state ) { export function getEditorCanvasContainerView( state ) { return state.editorCanvasContainerView; } + +export function getRoutes( state ) { + return state.routes; +} diff --git a/packages/edit-site/src/store/reducer.js b/packages/edit-site/src/store/reducer.js index 1e3d9c43f0eb34..951f004adc9af5 100644 --- a/packages/edit-site/src/store/reducer.js +++ b/packages/edit-site/src/store/reducer.js @@ -98,10 +98,20 @@ function editorCanvasContainerView( state = undefined, action ) { return state; } +function routes( state = [], action ) { + switch ( action.type ) { + case 'REGISTER_ROUTE': + return [ ...state, action.route ]; + } + + return state; +} + export default combineReducers( { settings, editedPost, saveViewPanel, canvasMode, editorCanvasContainerView, + routes, } ); From f83c59253907969de16e172eb5a0fc7bc1f90f9c Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 17 Oct 2024 14:04:32 +0100 Subject: [PATCH 03/30] Zoom-out: Rely on zoom-level instead of zoom-out mode (#66141) Co-authored-by: youknowriad Co-authored-by: ellatrix Co-authored-by: ntsekouras Co-authored-by: jeryj Co-authored-by: getdave Co-authored-by: draganescu --- .../src/components/block-list/index.js | 6 +-- .../use-focus-first-element.js | 8 +-- .../use-selected-block-event-handlers.js | 27 +++------- .../use-block-props/use-zoom-out-mode-exit.js | 24 ++------- .../block-list/use-in-between-inserter.js | 2 +- .../src/components/block-toolbar/index.js | 6 +-- .../src/components/block-tools/index.js | 6 +-- .../components/block-tools/insertion-point.js | 7 +-- .../src/components/inner-blocks/index.js | 4 +- .../pattern-category-previews.js | 4 +- .../src/components/inserter/menu.js | 4 +- .../src/components/list-view/appender.js | 11 ++-- .../src/components/tool-selector/index.js | 3 +- .../components/use-block-drop-zone/index.js | 8 +-- .../components/writing-flow/use-tab-nav.js | 3 +- .../block-editor/src/hooks/use-zoom-out.js | 46 +++++----------- packages/block-editor/src/store/actions.js | 43 +-------------- .../block-editor/src/store/private-actions.js | 54 +++++++++++++++++-- .../src/store/private-selectors.js | 25 +-------- packages/block-editor/src/store/selectors.js | 9 ++-- .../edit-post/src/components/layout/index.js | 4 +- .../components/layout/use-should-iframe.js | 9 +++- .../src/components/document-tools/index.js | 4 +- .../src/components/inserter-sidebar/index.js | 9 ++-- .../src/components/zoom-out-toggle/index.js | 3 +- packages/editor/src/store/selectors.js | 5 +- 26 files changed, 133 insertions(+), 201 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index e2e019d4a9bf69..8e8f69f46ae276 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -181,8 +181,8 @@ function Items( { __unstableGetVisibleBlocks, getTemplateLock, getBlockEditingMode, - __unstableGetEditorMode, isSectionBlock, + isZoomOut: _isZoomOut, } = unlock( select( blockEditorStore ) ); const _order = getBlockOrder( rootClientId ); @@ -200,13 +200,13 @@ function Items( { order: _order, selectedBlocks: getSelectedBlockClientIds(), visibleBlocks: __unstableGetVisibleBlocks(), - isZoomOut: __unstableGetEditorMode() === 'zoom-out', + isZoomOut: _isZoomOut(), shouldRenderAppender: ! isSectionBlock( rootClientId ) && getBlockEditingMode( rootClientId ) !== 'disabled' && ! getTemplateLock( rootClientId ) && hasAppender && - __unstableGetEditorMode() !== 'zoom-out' && + ! _isZoomOut() && ( hasCustomAppender || rootClientId === selectedBlockClientId || ( ! rootClientId && diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js b/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js index 27f72d1a100d3e..26f7cca2990d89 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-focus-first-element.js @@ -15,6 +15,7 @@ import { useSelect } from '@wordpress/data'; */ import { isInsideRootBlock } from '../../../utils/dom'; import { store as blockEditorStore } from '../../../store'; +import { unlock } from '../../../lock-unlock'; /** @typedef {import('@wordpress/element').RefObject} RefObject */ @@ -28,15 +29,16 @@ import { store as blockEditorStore } from '../../../store'; */ export function useFocusFirstElement( { clientId, initialPosition } ) { const ref = useRef(); - const { isBlockSelected, isMultiSelecting, __unstableGetEditorMode } = - useSelect( blockEditorStore ); + const { isBlockSelected, isMultiSelecting, isZoomOut } = unlock( + useSelect( blockEditorStore ) + ); useEffect( () => { // Check if the block is still selected at the time this effect runs. if ( ! isBlockSelected( clientId ) || isMultiSelecting() || - __unstableGetEditorMode() === 'zoom-out' + isZoomOut() ) { return; } diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js index 19b778ca8fccfc..68f8a671adbe9a 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-selected-block-event-handlers.js @@ -21,18 +21,12 @@ import { unlock } from '../../../lock-unlock'; * @param {string} clientId Block client ID. */ export function useEventHandlers( { clientId, isSelected } ) { - const { - getBlockRootClientId, - getBlockIndex, - isZoomOut, - __unstableGetEditorMode, - } = unlock( useSelect( blockEditorStore ) ); - const { - insertAfterBlock, - removeBlock, - __unstableSetEditorMode, - resetZoomLevel, - } = unlock( useDispatch( blockEditorStore ) ); + const { getBlockRootClientId, getBlockIndex, isZoomOut } = unlock( + useSelect( blockEditorStore ) + ); + const { insertAfterBlock, removeBlock, resetZoomLevel } = unlock( + useDispatch( blockEditorStore ) + ); return useRefEffect( ( node ) => { @@ -66,12 +60,7 @@ export function useEventHandlers( { clientId, isSelected } ) { event.preventDefault(); - if ( - keyCode === ENTER && - __unstableGetEditorMode() === 'zoom-out' && - isZoomOut() - ) { - __unstableSetEditorMode( 'edit' ); + if ( keyCode === ENTER && isZoomOut() ) { resetZoomLevel(); } else if ( keyCode === ENTER ) { insertAfterBlock( clientId ); @@ -105,8 +94,6 @@ export function useEventHandlers( { clientId, isSelected } ) { getBlockIndex, insertAfterBlock, removeBlock, - __unstableGetEditorMode, - __unstableSetEditorMode, isZoomOut, resetZoomLevel, ] diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-zoom-out-mode-exit.js b/packages/block-editor/src/components/block-list/use-block-props/use-zoom-out-mode-exit.js index 494694952110bb..d77af3ef89c1ec 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-zoom-out-mode-exit.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-zoom-out-mode-exit.js @@ -14,22 +14,13 @@ import { unlock } from '../../../lock-unlock'; * Allows Zoom Out mode to be exited by double clicking in the selected block. */ export function useZoomOutModeExit() { - const { getSettings, isZoomOut, __unstableGetEditorMode } = unlock( - useSelect( blockEditorStore ) - ); - - const { __unstableSetEditorMode, resetZoomLevel } = unlock( - useDispatch( blockEditorStore ) - ); + const { getSettings, isZoomOut } = unlock( useSelect( blockEditorStore ) ); + const { resetZoomLevel } = unlock( useDispatch( blockEditorStore ) ); return useRefEffect( ( node ) => { function onDoubleClick( event ) { - // In "compose" mode. - const composeMode = - __unstableGetEditorMode() === 'zoom-out' && isZoomOut(); - - if ( ! composeMode ) { + if ( ! isZoomOut() ) { return; } @@ -43,7 +34,6 @@ export function useZoomOutModeExit() { ) { __experimentalSetIsInserterOpened( false ); } - __unstableSetEditorMode( 'edit' ); resetZoomLevel(); } } @@ -54,12 +44,6 @@ export function useZoomOutModeExit() { node.removeEventListener( 'dblclick', onDoubleClick ); }; }, - [ - getSettings, - __unstableSetEditorMode, - __unstableGetEditorMode, - isZoomOut, - resetZoomLevel, - ] + [ getSettings, isZoomOut, resetZoomLevel ] ); } diff --git a/packages/block-editor/src/components/block-list/use-in-between-inserter.js b/packages/block-editor/src/components/block-list/use-in-between-inserter.js index 2b76804785a576..71eda2558f7930 100644 --- a/packages/block-editor/src/components/block-list/use-in-between-inserter.js +++ b/packages/block-editor/src/components/block-list/use-in-between-inserter.js @@ -18,7 +18,7 @@ export function useInBetweenInserter() { const isInBetweenInserterDisabled = useSelect( ( select ) => select( blockEditorStore ).getSettings().isDistractionFree || - select( blockEditorStore ).__unstableGetEditorMode() === 'zoom-out', + unlock( select( blockEditorStore ) ).isZoomOut(), [] ); const { diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index d6a0985fef3610..58a7b2b09bb2cd 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -81,7 +81,7 @@ export function PrivateBlockToolbar( { getBlockParentsByBlockName, getTemplateLock, getParentSectionBlock, - isZoomOutMode, + isZoomOut, } = unlock( select( blockEditorStore ) ); const selectedBlockClientIds = getSelectedBlockClientIds(); const selectedBlockClientId = selectedBlockClientIds[ 0 ]; @@ -122,7 +122,7 @@ export function PrivateBlockToolbar( { shouldShowVisualToolbar: isValid && isVisual, toolbarKey: `${ selectedBlockClientId }${ parentClientId }`, showParentSelector: - ! isZoomOutMode() && + ! isZoomOut() && parentBlockType && getBlockEditingMode( parentClientId ) !== 'disabled' && hasBlockSupport( @@ -134,7 +134,7 @@ export function PrivateBlockToolbar( { isUsingBindings: _isUsingBindings, hasParentPattern: _hasParentPattern, hasContentOnlyLocking: _hasTemplateLock, - showShuffleButton: isZoomOutMode(), + showShuffleButton: isZoomOut(), }; }, [] ); diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index e07e9b8f4bf5c1..700b345be20274 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -30,21 +30,19 @@ function selector( select ) { getSelectedBlockClientId, getFirstMultiSelectedBlockClientId, getSettings, - __unstableGetEditorMode, isTyping, isDragging, + isZoomOut, } = unlock( select( blockEditorStore ) ); const clientId = getSelectedBlockClientId() || getFirstMultiSelectedBlockClientId(); - const editorMode = __unstableGetEditorMode(); - return { clientId, hasFixedToolbar: getSettings().hasFixedToolbar, isTyping: isTyping(), - isZoomOutMode: editorMode === 'zoom-out', + isZoomOutMode: isZoomOut(), isDragging: isDragging(), }; } diff --git a/packages/block-editor/src/components/block-tools/insertion-point.js b/packages/block-editor/src/components/block-tools/insertion-point.js index 891a32eaa5dc9c..6c54993b23bcaa 100644 --- a/packages/block-editor/src/components/block-tools/insertion-point.js +++ b/packages/block-editor/src/components/block-tools/insertion-point.js @@ -18,6 +18,7 @@ import Inserter from '../inserter'; import { store as blockEditorStore } from '../../store'; import BlockPopoverInbetween from '../block-popover/inbetween'; import BlockDropZonePopover from '../block-popover/drop-zone'; +import { unlock } from '../../lock-unlock'; export const InsertionPointOpenRef = createContext(); @@ -47,8 +48,8 @@ function InbetweenInsertionPointPopover( { getPreviousBlockClientId, getNextBlockClientId, getSettings, - __unstableGetEditorMode, - } = select( blockEditorStore ); + isZoomOut, + } = unlock( select( blockEditorStore ) ); const insertionPoint = getBlockInsertionPoint(); const order = getBlockOrder( insertionPoint.rootClientId ); @@ -78,7 +79,7 @@ function InbetweenInsertionPointPopover( { rootClientId: insertionPoint.rootClientId, isDistractionFree: settings.isDistractionFree, isInserterShown: insertionPoint?.__unstableWithInserter, - isZoomOutMode: __unstableGetEditorMode() === 'zoom-out', + isZoomOutMode: isZoomOut(), }; }, [] ); const { getBlockEditingMode } = useSelect( blockEditorStore ); diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index 1583031a8ea18d..e79c188018cb50 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -195,7 +195,7 @@ export function useInnerBlocksProps( props = {}, options = {} ) { ( select ) => { const { getBlockName, - __unstableGetEditorMode, + isZoomOut, getTemplateLock, getBlockRootClientId, getBlockEditingMode, @@ -216,7 +216,7 @@ export function useInnerBlocksProps( props = {}, options = {} ) { _isDropZoneDisabled = blockEditingMode === 'disabled'; - if ( __unstableGetEditorMode() === 'zoom-out' ) { + if ( isZoomOut() ) { // In zoom out mode, we want to disable the drop zone for the sections. // The inner blocks belonging to the section drop zone is // already disabled by the blocks themselves being disabled. diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js index 61716f616dafa9..835015704c944b 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab/pattern-category-previews.js @@ -34,6 +34,7 @@ import { INSERTER_PATTERN_TYPES, } from './utils'; import { store as blockEditorStore } from '../../../store'; +import { unlock } from '../../../lock-unlock'; const noop = () => {}; @@ -45,8 +46,7 @@ export function PatternCategoryPreviews( { showTitlesAsTooltip, } ) { const isZoomOutMode = useSelect( - ( select ) => - select( blockEditorStore ).__unstableGetEditorMode() === 'zoom-out', + ( select ) => unlock( select( blockEditorStore ) ).isZoomOut(), [] ); const [ allPatterns, , onClickPattern ] = usePatternsState( diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index bdd4ff11abceee..915a36d242ba26 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -33,6 +33,7 @@ import useInsertionPoint from './hooks/use-insertion-point'; import { store as blockEditorStore } from '../../store'; import TabbedSidebar from '../tabbed-sidebar'; import { useZoomOut } from '../../hooks/use-zoom-out'; +import { unlock } from '../../lock-unlock'; const NOOP = () => {}; function InserterMenu( @@ -54,8 +55,7 @@ function InserterMenu( ref ) { const isZoomOutMode = useSelect( - ( select ) => - select( blockEditorStore ).__unstableGetEditorMode() === 'zoom-out', + ( select ) => unlock( select( blockEditorStore ) ).isZoomOut(), [] ); const [ filterValue, setFilterValue, delayedFilterValue ] = diff --git a/packages/block-editor/src/components/list-view/appender.js b/packages/block-editor/src/components/list-view/appender.js index ec46a1d211ab65..8cd515499b1a61 100644 --- a/packages/block-editor/src/components/list-view/appender.js +++ b/packages/block-editor/src/components/list-view/appender.js @@ -15,6 +15,7 @@ import useBlockDisplayTitle from '../block-title/use-block-display-title'; import { useListViewContext } from './context'; import Inserter from '../inserter'; import AriaReferencedText from './aria-referenced-text'; +import { unlock } from '../../lock-unlock'; export const Appender = forwardRef( ( { nestingLevel, blockCount, clientId, ...props }, ref ) => { @@ -23,13 +24,11 @@ export const Appender = forwardRef( const instanceId = useInstanceId( Appender ); const hideInserter = useSelect( ( select ) => { - const { getTemplateLock, __unstableGetEditorMode } = - select( blockEditorStore ); - - return ( - !! getTemplateLock( clientId ) || - __unstableGetEditorMode() === 'zoom-out' + const { getTemplateLock, isZoomOut } = unlock( + select( blockEditorStore ) ); + + return !! getTemplateLock( clientId ) || isZoomOut(); }, [ clientId ] ); diff --git a/packages/block-editor/src/components/tool-selector/index.js b/packages/block-editor/src/components/tool-selector/index.js index 14a7f00593e196..d4ba5d58ea61eb 100644 --- a/packages/block-editor/src/components/tool-selector/index.js +++ b/packages/block-editor/src/components/tool-selector/index.js @@ -36,7 +36,7 @@ function ToolSelector( props, ref ) { ( select ) => select( blockEditorStore ).__unstableGetEditorMode(), [] ); - const { resetZoomLevel, __unstableSetEditorMode } = unlock( + const { __unstableSetEditorMode } = unlock( useDispatch( blockEditorStore ) ); @@ -68,7 +68,6 @@ function ToolSelector( props, ref ) { mode === 'navigation' ? 'navigation' : 'edit' } onSelect={ ( newMode ) => { - resetZoomLevel(); __unstableSetEditorMode( newMode ); } } choices={ [ diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js index ff4d52aaa493bc..64424178461bcf 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/index.js @@ -330,7 +330,7 @@ export default function useBlockDropZone( { getAllowedBlocks, isDragging, isGroupable, - isZoomOutMode, + isZoomOut, getSectionRootClientId, } = unlock( useSelect( blockEditorStore ) ); const { @@ -383,7 +383,7 @@ export default function useBlockDropZone( { // do not allow dropping as the drop target is not within the root (that which is // treated as "the content" by Zoom Out Mode). if ( - isZoomOutMode() && + isZoomOut() && sectionRootClientId !== targetRootClientId ) { return; @@ -439,7 +439,7 @@ export default function useBlockDropZone( { const [ targetIndex, operation, nearestSide ] = dropTargetPosition; - if ( isZoomOutMode() && operation !== 'insert' ) { + if ( isZoomOut() && operation !== 'insert' ) { return; } @@ -514,7 +514,7 @@ export default function useBlockDropZone( { getDraggedBlockClientIds, getBlockType, getSectionRootClientId, - isZoomOutMode, + isZoomOut, getBlocks, getBlockListSettings, dropZoneElement, diff --git a/packages/block-editor/src/components/writing-flow/use-tab-nav.js b/packages/block-editor/src/components/writing-flow/use-tab-nav.js index 216e7b6e04ad57..16a18358fb2ede 100644 --- a/packages/block-editor/src/components/writing-flow/use-tab-nav.js +++ b/packages/block-editor/src/components/writing-flow/use-tab-nav.js @@ -27,7 +27,6 @@ export default function useTabNav() { getLastFocus, getSectionRootClientId, isZoomOut, - __unstableGetEditorMode, } = unlock( useSelect( blockEditorStore ) ); const { setLastFocus } = unlock( useDispatch( blockEditorStore ) ); @@ -54,7 +53,7 @@ export default function useTabNav() { } } // In "compose" mode without a selected ID, we want to place focus on the section root when tabbing to the canvas. - else if ( __unstableGetEditorMode() === 'zoom-out' && isZoomOut() ) { + else if ( isZoomOut() ) { const sectionRootClientId = getSectionRootClientId(); const sectionBlocks = getBlockOrder( sectionRootClientId ); diff --git a/packages/block-editor/src/hooks/use-zoom-out.js b/packages/block-editor/src/hooks/use-zoom-out.js index 23511487a54bf7..23f7fbc4bd59a1 100644 --- a/packages/block-editor/src/hooks/use-zoom-out.js +++ b/packages/block-editor/src/hooks/use-zoom-out.js @@ -2,62 +2,42 @@ * WordPress dependencies */ import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect, useRef } from '@wordpress/element'; +import { useEffect } from '@wordpress/element'; /** * Internal dependencies */ import { store as blockEditorStore } from '../store'; import { unlock } from '../lock-unlock'; + /** * A hook used to set the editor mode to zoomed out mode, invoking the hook sets the mode. * * @param {boolean} zoomOut If we should enter into zoomOut mode or not */ export function useZoomOut( zoomOut = true ) { - const { __unstableSetEditorMode, setZoomLevel } = unlock( + const { setZoomLevel, resetZoomLevel } = unlock( useDispatch( blockEditorStore ) ); - const { __unstableGetEditorMode } = unlock( useSelect( blockEditorStore ) ); - - const originalEditingModeRef = useRef( null ); - const mode = __unstableGetEditorMode(); + const { isZoomOut } = unlock( useSelect( blockEditorStore ) ); useEffect( () => { - // Only set this on mount so we know what to return to when we unmount. - if ( ! originalEditingModeRef.current ) { - originalEditingModeRef.current = mode; - } + const isZoomOutOnMount = isZoomOut(); return () => { - // We need to use __unstableGetEditorMode() here and not `mode`, as mode may not update on unmount - if ( - __unstableGetEditorMode() === 'zoom-out' && - __unstableGetEditorMode() !== originalEditingModeRef.current - ) { - __unstableSetEditorMode( originalEditingModeRef.current ); - setZoomLevel( 100 ); + if ( isZoomOutOnMount ) { + setZoomLevel( 50 ); + } else { + resetZoomLevel(); } }; }, [] ); - // The effect opens the zoom-out view if we want it open and it's not currently in zoom-out mode. useEffect( () => { - if ( zoomOut && mode !== 'zoom-out' ) { - __unstableSetEditorMode( 'zoom-out' ); + if ( zoomOut ) { setZoomLevel( 50 ); - } else if ( - ! zoomOut && - __unstableGetEditorMode() === 'zoom-out' && - originalEditingModeRef.current !== mode - ) { - __unstableSetEditorMode( originalEditingModeRef.current ); - setZoomLevel( 100 ); + } else { + resetZoomLevel(); } - }, [ - __unstableGetEditorMode, - __unstableSetEditorMode, - zoomOut, - setZoomLevel, - ] ); // Mode is deliberately excluded from the dependencies so that the effect does not run when mode changes. + }, [ zoomOut, setZoomLevel, resetZoomLevel ] ); } diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index d92f8bc08569dc..24a08ecb21d6e5 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1669,46 +1669,7 @@ export const setNavigationMode = */ export const __unstableSetEditorMode = ( mode ) => - ( { dispatch, select, registry } ) => { - // When switching to zoom-out mode, we need to select the parent section - if ( mode === 'zoom-out' ) { - const firstSelectedClientId = select.getBlockSelectionStart(); - - const sectionRootClientId = select.getSectionRootClientId(); - - if ( firstSelectedClientId ) { - let sectionClientId; - - if ( sectionRootClientId ) { - const sectionClientIds = - select.getBlockOrder( sectionRootClientId ); - - // If the selected block is a section block, use it. - if ( sectionClientIds?.includes( firstSelectedClientId ) ) { - sectionClientId = firstSelectedClientId; - } else { - // If the selected block is not a section block, find - // the parent section that contains the selected block. - sectionClientId = select - .getBlockParents( firstSelectedClientId ) - .find( ( parent ) => - sectionClientIds.includes( parent ) - ); - } - } else { - sectionClientId = select.getBlockHierarchyRootClientId( - firstSelectedClientId - ); - } - - if ( sectionClientId ) { - dispatch.selectBlock( sectionClientId ); - } else { - dispatch.clearSelectedBlock(); - } - } - } - + ( { registry } ) => { registry.dispatch( preferencesStore ).set( 'core', 'editorTool', mode ); if ( mode === 'navigation' ) { @@ -1723,8 +1684,6 @@ export const __unstableSetEditorMode = 'You are currently in edit mode. To return to the navigation mode, press Escape.' ) ); - } else if ( mode === 'zoom-out' ) { - speak( __( 'You are currently in zoom-out mode.' ) ); } }; diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index 5571db0ce91066..0ca3eb2cf522e6 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -2,6 +2,8 @@ * WordPress dependencies */ import { Platform } from '@wordpress/element'; +import { speak } from '@wordpress/a11y'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -404,12 +406,54 @@ export const modifyContentLockBlock = * @param {number} zoom the new zoom level * @return {Object} Action object. */ -export function setZoomLevel( zoom = 100 ) { - return { - type: 'SET_ZOOM_LEVEL', - zoom, +export const setZoomLevel = + ( zoom = 100 ) => + ( { select, dispatch } ) => { + // When switching to zoom-out mode, we need to select the parent section + if ( zoom !== 100 ) { + const firstSelectedClientId = select.getBlockSelectionStart(); + const sectionRootClientId = select.getSectionRootClientId(); + + if ( firstSelectedClientId ) { + let sectionClientId; + + if ( sectionRootClientId ) { + const sectionClientIds = + select.getBlockOrder( sectionRootClientId ); + + // If the selected block is a section block, use it. + if ( sectionClientIds?.includes( firstSelectedClientId ) ) { + sectionClientId = firstSelectedClientId; + } else { + // If the selected block is not a section block, find + // the parent section that contains the selected block. + sectionClientId = select + .getBlockParents( firstSelectedClientId ) + .find( ( parent ) => + sectionClientIds.includes( parent ) + ); + } + } else { + sectionClientId = select.getBlockHierarchyRootClientId( + firstSelectedClientId + ); + } + + if ( sectionClientId ) { + dispatch.selectBlock( sectionClientId ); + } else { + dispatch.clearSelectedBlock(); + } + + speak( __( 'You are currently in zoom-out mode.' ) ); + } + } + + dispatch( { + type: 'SET_ZOOM_LEVEL', + zoom, + } ); }; -} /** * Resets the Zoom state. diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 28e0c2bd47b0ca..dff5dc0184a4d2 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -17,7 +17,6 @@ import { getClientIdsWithDescendants, isNavigationMode, getBlockRootClientId, - __unstableGetEditorMode, } from './selectors'; import { checkAllowListRecursive, @@ -118,6 +117,7 @@ export const getEnabledClientIdsTree = createSelector( state.settings.templateLock, state.blockListSettings, state.editorMode, + state.zoomLevel, getSectionRootClientId( state ), ] ); @@ -572,17 +572,6 @@ export const getBlockStyles = createSelector( ] ); -/** - * Returns whether zoom out mode is enabled. - * - * @param {Object} state Editor state. - * - * @return {boolean} Is zoom out mode enabled. - */ -export function isZoomOutMode( state ) { - return __unstableGetEditorMode( state ) === 'zoom-out'; -} - /** * Retrieves the client ID of the block which contains the blocks * acting as "sections" in the editor. This is typically the "main content" @@ -596,16 +585,6 @@ export function getSectionRootClientId( state ) { return state.settings?.[ sectionRootClientIdKey ]; } -/** - * Returns the zoom out state. - * - * @param {Object} state Global application state. - * @return {boolean} The zoom out state. - */ -export function getZoomLevel( state ) { - return state.zoomLevel; -} - /** * Returns whether the editor is considered zoomed out. * @@ -613,7 +592,7 @@ export function getZoomLevel( state ) { * @return {boolean} Whether the editor is zoomed. */ export function isZoomOut( state ) { - return getZoomLevel( state ) < 100; + return state.zoomLevel < 100; } /** diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 57e48442b1070b..210cd26aeaa954 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -41,6 +41,7 @@ import { getSectionRootClientId, isSectionBlock, getParentSectionBlock, + isZoomOut, } from './private-selectors'; /** @@ -2902,10 +2903,8 @@ export function __unstableHasActiveBlockOverlayActive( state, clientId ) { return true; } - const editorMode = __unstableGetEditorMode( state ); - // In zoom-out mode, the block overlay is always active for section level blocks. - if ( editorMode === 'zoom-out' ) { + if ( isZoomOut( state ) ) { const sectionRootClientId = getSectionRootClientId( state ); if ( sectionRootClientId ) { const sectionClientIds = getBlockOrder( @@ -3004,8 +3003,7 @@ export const getBlockEditingMode = createRegistrySelector( // In zoom-out mode, override the behavior set by // __unstableSetBlockEditingMode to only allow editing the top-level // sections. - const editorMode = __unstableGetEditorMode( state ); - if ( editorMode === 'zoom-out' ) { + if ( isZoomOut( state ) ) { const sectionRootClientId = getSectionRootClientId( state ); if ( clientId === '' /* ROOT_CONTAINER_CLIENT_ID */ ) { @@ -3027,6 +3025,7 @@ export const getBlockEditingMode = createRegistrySelector( return 'disabled'; } + const editorMode = __unstableGetEditorMode( state ); if ( editorMode === 'navigation' ) { const sectionRootClientId = getSectionRootClientId( state ); diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 88d0cd1588f03f..42250054a70873 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -411,7 +411,7 @@ function Layout( { kind: 'postType', name: 'wp_template', } ); - const { __unstableGetEditorMode } = select( blockEditorStore ); + const { isZoomOut } = unlock( select( blockEditorStore ) ); const { getEditorMode, getRenderingMode } = select( editorStore ); const isRenderingPostOnly = getRenderingMode() === 'post-only'; @@ -436,7 +436,7 @@ function Layout( { ? getEditedPostTemplateId() : null, enablePaddingAppender: - __unstableGetEditorMode() !== 'zoom-out' && + ! isZoomOut() && isRenderingPostOnly && ! DESIGN_POST_TYPES.includes( currentPostType ), }; diff --git a/packages/edit-post/src/components/layout/use-should-iframe.js b/packages/edit-post/src/components/layout/use-should-iframe.js index e36a4773c4a1fd..97e746a6a28f6e 100644 --- a/packages/edit-post/src/components/layout/use-should-iframe.js +++ b/packages/edit-post/src/components/layout/use-should-iframe.js @@ -6,6 +6,11 @@ import { useSelect } from '@wordpress/data'; import { store as blocksStore } from '@wordpress/blocks'; import { store as blockEditorStore } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + const isGutenbergPlugin = globalThis.IS_GUTENBERG_PLUGIN ? true : false; export function useShouldIframe() { @@ -16,7 +21,7 @@ export function useShouldIframe() { isZoomOutMode, } = useSelect( ( select ) => { const { getEditorSettings, getCurrentPostType } = select( editorStore ); - const { __unstableGetEditorMode } = select( blockEditorStore ); + const { isZoomOut } = unlock( select( blockEditorStore ) ); const { getBlockTypes } = select( blocksStore ); const editorSettings = getEditorSettings(); return { @@ -25,7 +30,7 @@ export function useShouldIframe() { return type.apiVersion >= 3; } ), isEditingTemplate: getCurrentPostType() === 'wp_template', - isZoomOutMode: __unstableGetEditorMode() === 'zoom-out', + isZoomOutMode: isZoomOut(), }; }, [] ); diff --git a/packages/editor/src/components/document-tools/index.js b/packages/editor/src/components/document-tools/index.js index 146945f7343bf2..ee261567a9115a 100644 --- a/packages/editor/src/components/document-tools/index.js +++ b/packages/editor/src/components/document-tools/index.js @@ -48,7 +48,7 @@ function DocumentTools( { className, disableBlockTools = false } ) { getListViewToggleRef, } = unlock( select( editorStore ) ); const { getShortcutRepresentation } = select( keyboardShortcutsStore ); - const { __unstableGetEditorMode } = select( blockEditorStore ); + const { isZoomOut } = unlock( select( blockEditorStore ) ); return { isInserterOpened: select( editorStore ).isInserterOpened(), @@ -61,7 +61,7 @@ function DocumentTools( { className, disableBlockTools = false } ) { showIconLabels: get( 'core', 'showIconLabels' ), isDistractionFree: get( 'core', 'distractionFree' ), isVisualMode: getEditorMode() === 'visual', - isZoomedOutView: __unstableGetEditorMode() === 'zoom-out', + isZoomedOutView: isZoomOut(), }; }, [] ); diff --git a/packages/editor/src/components/inserter-sidebar/index.js b/packages/editor/src/components/inserter-sidebar/index.js index 5cace042fae58c..66730b43592b6e 100644 --- a/packages/editor/src/components/inserter-sidebar/index.js +++ b/packages/editor/src/components/inserter-sidebar/index.js @@ -33,15 +33,12 @@ export default function InserterSidebar() { getInserter, isPublishSidebarOpened, } = unlock( select( editorStore ) ); - const { - getBlockRootClientId, - __unstableGetEditorMode, - getSectionRootClientId, - } = unlock( select( blockEditorStore ) ); + const { getBlockRootClientId, isZoomOut, getSectionRootClientId } = + unlock( select( blockEditorStore ) ); const { get } = select( preferencesStore ); const { getActiveComplementaryArea } = select( interfaceStore ); const getBlockSectionRootClientId = () => { - if ( __unstableGetEditorMode() === 'zoom-out' ) { + if ( isZoomOut() ) { const sectionRootClientId = getSectionRootClientId(); if ( sectionRootClientId ) { diff --git a/packages/editor/src/components/zoom-out-toggle/index.js b/packages/editor/src/components/zoom-out-toggle/index.js index b89bf15546f0d8..2cfb2e2da3bc44 100644 --- a/packages/editor/src/components/zoom-out-toggle/index.js +++ b/packages/editor/src/components/zoom-out-toggle/index.js @@ -23,7 +23,7 @@ const ZoomOutToggle = () => { ), } ) ); - const { resetZoomLevel, setZoomLevel, __unstableSetEditorMode } = unlock( + const { resetZoomLevel, setZoomLevel } = unlock( useDispatch( blockEditorStore ) ); @@ -33,7 +33,6 @@ const ZoomOutToggle = () => { } else { setZoomLevel( 50 ); } - __unstableSetEditorMode( isZoomOut ? 'edit' : 'zoom-out' ); }; return ( diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index abb744ee5f2207..2396cb67c73e69 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -28,6 +28,7 @@ import { } from './constants'; import { getPostRawValue } from './reducer'; import { getTemplatePartIcon } from '../utils/get-template-part-icon'; +import { unlock } from '../lock-unlock'; /** * Shared reference to an empty object for cases where it is important to avoid @@ -1299,8 +1300,8 @@ export function getRenderingMode( state ) { */ export const getDeviceType = createRegistrySelector( ( select ) => ( state ) => { - const editorMode = select( blockEditorStore ).__unstableGetEditorMode(); - if ( editorMode === 'zoom-out' ) { + const isZoomOut = unlock( select( blockEditorStore ) ).isZoomOut(); + if ( isZoomOut ) { return 'Desktop'; } return state.deviceType; From ff1d1142d72a5bf79c6138a59c6c66c7d0e1d75d Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 17 Oct 2024 17:16:37 +0200 Subject: [PATCH 04/30] Tabs: fix animation timings (#66198) Co-authored-by: ciampo Co-authored-by: tyxla Co-authored-by: t-hamano --- .../src/components/inserter/category-tabs/index.js | 1 - packages/components/CHANGELOG.md | 1 + packages/components/src/tabs/styles.ts | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/inserter/category-tabs/index.js b/packages/block-editor/src/components/inserter/category-tabs/index.js index 6d8f5fcbca1334..2641e2a20c40f6 100644 --- a/packages/block-editor/src/components/inserter/category-tabs/index.js +++ b/packages/block-editor/src/components/inserter/category-tabs/index.js @@ -33,7 +33,6 @@ function CategoryTabs( { return ( Date: Thu, 17 Oct 2024 16:22:51 +0100 Subject: [PATCH 05/30] BlockGroupToolbar: Better i18n context for toolbar labels (#66211) Make it less confusing for translators to translate the actions in BlockGroupToolbar, specifically the buttons to convert a selection of blocks to a Group, a Row, a Stack or a Grid. --- .../src/components/convert-to-group-buttons/toolbar.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/convert-to-group-buttons/toolbar.js b/packages/block-editor/src/components/convert-to-group-buttons/toolbar.js index 21934bfd603f52..b82205d0c46616 100644 --- a/packages/block-editor/src/components/convert-to-group-buttons/toolbar.js +++ b/packages/block-editor/src/components/convert-to-group-buttons/toolbar.js @@ -85,27 +85,27 @@ function BlockGroupToolbar() { { canInsertRow && ( ) } { canInsertStack && ( ) } { canInsertGrid && ( ) } From 605e9df6f09861c56b7f1c171dc9a7412a52713e Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 17 Oct 2024 17:24:30 +0200 Subject: [PATCH 06/30] Tabs and TabPanel: Fix arrow key navigation in RTL (#66201) Co-authored-by: ciampo Co-authored-by: tyxla Co-authored-by: t-hamano --- packages/components/CHANGELOG.md | 1 + packages/components/src/tab-panel/index.tsx | 2 ++ packages/components/src/tabs/index.tsx | 2 ++ 3 files changed, 5 insertions(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2064aa9e9e9621..9c36c6600ac701 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -6,6 +6,7 @@ - `Tabs`: fix indicator animation ([#66198](https://github.com/WordPress/gutenberg/pull/66198)). - `ColorPalette`: prevent overflow of custom color button background ([#66152](https://github.com/WordPress/gutenberg/pull/66152)). +- `Tabs` and `TabPanel`: Fix arrow key navigation in RTL ([#66201](https://github.com/WordPress/gutenberg/pull/66201)). ### Enhancements diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx index 9e180c30321b93..f9db745b424260 100644 --- a/packages/components/src/tab-panel/index.tsx +++ b/packages/components/src/tab-panel/index.tsx @@ -16,6 +16,7 @@ import { useCallback, } from '@wordpress/element'; import { useInstanceId, usePrevious } from '@wordpress/compose'; +import { isRTL } from '@wordpress/i18n'; /** * Internal dependencies @@ -120,6 +121,7 @@ const UnforwardedTabPanel = ( orientation, selectOnMove, defaultSelectedId: prependInstanceId( initialTabName ), + rtl: isRTL(), } ); const selectedTabName = extractTabName( diff --git a/packages/components/src/tabs/index.tsx b/packages/components/src/tabs/index.tsx index ca8c5b19bcd622..09225c0cce9b8d 100644 --- a/packages/components/src/tabs/index.tsx +++ b/packages/components/src/tabs/index.tsx @@ -14,6 +14,7 @@ import { useMemo, useRef, } from '@wordpress/element'; +import { isRTL } from '@wordpress/i18n'; /** * Internal dependencies @@ -45,6 +46,7 @@ function Tabs( { onSelect?.( strippedDownId ); }, selectedId: selectedTabId && `${ instanceId }-${ selectedTabId }`, + rtl: isRTL(), } ); const isControlled = selectedTabId !== undefined; From 4c15400000023365ea5e414550656bd3be3167ec Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 17 Oct 2024 17:27:49 +0200 Subject: [PATCH 07/30] Tabs: simplify styled components code (#66208) Co-authored-by: ciampo Co-authored-by: tyxla --- packages/components/src/tabs/styles.ts | 2 +- packages/components/src/tabs/tablist.tsx | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/components/src/tabs/styles.ts b/packages/components/src/tabs/styles.ts index da7368c6e19114..36e0ed6f6b0147 100644 --- a/packages/components/src/tabs/styles.ts +++ b/packages/components/src/tabs/styles.ts @@ -11,7 +11,7 @@ import { COLORS, CONFIG } from '../utils'; import { space } from '../utils/space'; import Icon from '../icon'; -export const TabListWrapper = styled.div` +export const StyledTabList = styled( Ariakit.TabList )` display: flex; align-items: stretch; overflow-x: auto; diff --git a/packages/components/src/tabs/tablist.tsx b/packages/components/src/tabs/tablist.tsx index 512a3eb6724289..46cc0488bc67ae 100644 --- a/packages/components/src/tabs/tablist.tsx +++ b/packages/components/src/tabs/tablist.tsx @@ -1,7 +1,6 @@ /** * External dependencies */ -import * as Ariakit from '@ariakit/react'; import { useStoreState } from '@ariakit/react'; /** @@ -16,7 +15,7 @@ import { useMergeRefs } from '@wordpress/compose'; */ import type { TabListProps } from './types'; import { useTabsContext } from './context'; -import { TabListWrapper } from './styles'; +import { StyledTabList } from './styles'; import type { WordPressComponentProps } from '../context'; import clsx from 'clsx'; import type { ElementOffsetRect } from '../utils/element-rect'; @@ -109,10 +108,9 @@ export const TabList = forwardRef< } return ( - } onBlur={ onBlur } tabIndex={ -1 } data-select-on-move={ selectOnMove ? 'true' : 'false' } @@ -124,6 +122,6 @@ export const TabList = forwardRef< ) } > { children } - + ); } ); From 739805c4653a3b0227157bb3ebc2a27c15a22fc1 Mon Sep 17 00:00:00 2001 From: Maggie Date: Thu, 17 Oct 2024 17:52:16 +0200 Subject: [PATCH 08/30] Fix [Flaky Test] Entering zoomed out mode zooms the canvas (#66212) * start each test directly on the template page * remove pause Co-authored-by: MaggieCabrera Co-authored-by: talldan --- test/e2e/specs/site-editor/zoom-out.spec.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/e2e/specs/site-editor/zoom-out.spec.js b/test/e2e/specs/site-editor/zoom-out.spec.js index 2aabee07d18785..464bd4a4a4efad 100644 --- a/test/e2e/specs/site-editor/zoom-out.spec.js +++ b/test/e2e/specs/site-editor/zoom-out.spec.js @@ -12,9 +12,12 @@ test.describe( 'Zoom Out', () => { await requestUtils.activateTheme( 'twentytwentyone' ); } ); - test.beforeEach( async ( { admin, editor } ) => { - await admin.visitSiteEditor(); - await editor.canvas.locator( 'body' ).click(); + test.beforeEach( async ( { admin } ) => { + await admin.visitSiteEditor( { + postId: 'twentytwentyfour//index', + postType: 'wp_template', + canvas: 'edit', + } ); } ); test( 'Entering zoomed out mode zooms the canvas', async ( { From 51892ff1ca1284ab9672636420775041d2db0c0f Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Thu, 17 Oct 2024 13:26:56 +0000 Subject: [PATCH 09/30] Update changelog files --- packages/scripts/CHANGELOG.md | 6 ++++-- packages/scripts/package.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 41dcf4d54922f8..8a1133a2e7ae22 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -10,9 +10,11 @@ - Refactor to extract license related logic to a reusable module ([#66179](https://github.com/WordPress/gutenberg/pull/66179)). +## 30.3.0 (2024-10-17) + ### New Features -- Add new `build-blocks-manifest` command to generate a PHP file containing block metadata from all `block.json` files in a project ([#65866](https://github.com/WordPress/gutenberg/pull/65866)). +- Add new `build-blocks-manifest` command to generate a PHP file containing block metadata from all `block.json` files in a project ([#65866](https://github.com/WordPress/gutenberg/pull/65866)). ## 30.2.0 (2024-10-16) @@ -33,7 +35,7 @@ ### Enhancements -- Inlines CSS files imported from other CSS files before optimization in the `build` command ([#61121](https://github.com/WordPress/gutenberg/pull/61121)). +- Inlines CSS files imported from other CSS files before optimization in the `build` command ([#61121](https://github.com/WordPress/gutenberg/pull/61121)). ### Bug Fixes diff --git a/packages/scripts/package.json b/packages/scripts/package.json index c98f341b812655..121a9cb09dc8bc 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "30.2.0", + "version": "30.3.0-prerelease", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 99eae66d2707835ba3e1308ddb5cefa1859a8ef9 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Thu, 17 Oct 2024 13:28:40 +0000 Subject: [PATCH 10/30] chore(release): publish - @wordpress/e2e-tests@8.10.1 - @wordpress/scripts@30.3.0 --- package-lock.json | 4 ++-- packages/e2e-tests/package.json | 2 +- packages/scripts/package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d49fb33f5c7078..3c328a047fc588 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55085,7 +55085,7 @@ }, "packages/e2e-tests": { "name": "@wordpress/e2e-tests", - "version": "8.10.0", + "version": "8.10.1", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -56461,7 +56461,7 @@ }, "packages/scripts": { "name": "@wordpress/scripts", - "version": "30.2.0", + "version": "30.3.0", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 03f98a8289da4f..a2192c10049380 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "8.10.0", + "version": "8.10.1", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 121a9cb09dc8bc..66461f4eb28ec2 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "30.3.0-prerelease", + "version": "30.3.0", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 4f405ac3a70c555556e623d94cdf35f35f005b07 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 17 Oct 2024 18:01:56 +0200 Subject: [PATCH 11/30] RadioGroup: Fix arrow key navigation in RTL (#66202) Co-authored-by: ciampo Co-authored-by: tyxla --- packages/components/CHANGELOG.md | 1 + packages/components/src/radio-group/index.tsx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 9c36c6600ac701..475bf272cc1bb8 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -6,6 +6,7 @@ - `Tabs`: fix indicator animation ([#66198](https://github.com/WordPress/gutenberg/pull/66198)). - `ColorPalette`: prevent overflow of custom color button background ([#66152](https://github.com/WordPress/gutenberg/pull/66152)). +- `RadioGroup`: Fix arrow key navigation in RTL ([#66202](https://github.com/WordPress/gutenberg/pull/66202)). - `Tabs` and `TabPanel`: Fix arrow key navigation in RTL ([#66201](https://github.com/WordPress/gutenberg/pull/66201)). ### Enhancements diff --git a/packages/components/src/radio-group/index.tsx b/packages/components/src/radio-group/index.tsx index ac66c79e904e4b..e59775c00a8023 100644 --- a/packages/components/src/radio-group/index.tsx +++ b/packages/components/src/radio-group/index.tsx @@ -7,6 +7,7 @@ import * as Ariakit from '@ariakit/react'; * WordPress dependencies */ import { useMemo, forwardRef } from '@wordpress/element'; +import { isRTL } from '@wordpress/i18n'; /** * Internal dependencies @@ -34,6 +35,7 @@ function UnforwardedRadioGroup( setValue: ( newValue ) => { onChange?.( newValue ?? undefined ); }, + rtl: isRTL(), } ); const contextValue = useMemo( From b8cd03d2e2713ffe648923712d2599b25808ad67 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 17 Oct 2024 19:05:03 +0200 Subject: [PATCH 12/30] Tabs: override tablist's tabindex only when necessary (#66209) Co-authored-by: ciampo Co-authored-by: tyxla --- packages/components/CHANGELOG.md | 1 + packages/components/src/tabs/tablist.tsx | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 475bf272cc1bb8..53ff933848338c 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -8,6 +8,7 @@ - `ColorPalette`: prevent overflow of custom color button background ([#66152](https://github.com/WordPress/gutenberg/pull/66152)). - `RadioGroup`: Fix arrow key navigation in RTL ([#66202](https://github.com/WordPress/gutenberg/pull/66202)). - `Tabs` and `TabPanel`: Fix arrow key navigation in RTL ([#66201](https://github.com/WordPress/gutenberg/pull/66201)). +- `Tabs`: override tablist's tabindex only when necessary ([#66209](https://github.com/WordPress/gutenberg/pull/66209)). ### Enhancements diff --git a/packages/components/src/tabs/tablist.tsx b/packages/components/src/tabs/tablist.tsx index 46cc0488bc67ae..998da5707aa071 100644 --- a/packages/components/src/tabs/tablist.tsx +++ b/packages/components/src/tabs/tablist.tsx @@ -111,8 +111,15 @@ export const TabList = forwardRef< ( +
+ ) } onBlur={ onBlur } - tabIndex={ -1 } data-select-on-move={ selectOnMove ? 'true' : 'false' } { ...otherProps } className={ clsx( From 86b09fb68036d21a9ff78fc6036093f99ace816d Mon Sep 17 00:00:00 2001 From: James Koster Date: Fri, 18 Oct 2024 02:09:33 +0100 Subject: [PATCH 13/30] Add elevation tokens to storybook (#66122) Co-authored-by: jameskoster Co-authored-by: ciampo Co-authored-by: tyxla --- storybook/stories/tokens/components.tsx | 53 +++++++++++++++ storybook/stories/tokens/elevation.mdx | 88 +++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 storybook/stories/tokens/components.tsx create mode 100644 storybook/stories/tokens/elevation.mdx diff --git a/storybook/stories/tokens/components.tsx b/storybook/stories/tokens/components.tsx new file mode 100644 index 00000000000000..05c2b4bde85a21 --- /dev/null +++ b/storybook/stories/tokens/components.tsx @@ -0,0 +1,53 @@ +/** + * External dependencies + */ +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import React from 'react'; + +export const ElevationTable = ( { tokens } ) => { + return ( + + + + + + + + + + { tokens.map( ( { name, valueShow, valueCode } ) => ( + + + + + + ) ) } + +
TokenValueExample
{ name } + + { valueShow } + + +
+
+ ); +}; diff --git a/storybook/stories/tokens/elevation.mdx b/storybook/stories/tokens/elevation.mdx new file mode 100644 index 00000000000000..d00de6dbde7fab --- /dev/null +++ b/storybook/stories/tokens/elevation.mdx @@ -0,0 +1,88 @@ +import { Meta } from '@storybook/addon-docs/blocks'; +import { ElevationTable } from './components.tsx'; + + + +# Elevation tokens + +This document outlines the various tokens relating to elevation in the WordPress design system. + +## Values + +Tokens can be used in different ways, but regardless of which method is used, each one is meant to be used as the value of the CSS `box-shadow` property, and references the following values: + + + +## CSS tokens + +Elevation tokens are defined as SASS variables: + +- `$elevation-x-small` +- `$elevation-small` +- `$elevation-medium` +- `$elevation-large` + +They can be used like so: + +```css +.elevation-extra-small { + box-shadow: $elevation-x-small; +} +.elevation-small { + box-shadow: $elevation-small; +} +.elevation-medium { + box-shadow: $elevation-medium; +} +.elevation-large { + box-shadow: $elevation-large; +} +``` + +## JS tokens + +When working in the `@wordpress/components` package, the elevation tokens can also be consumed as JavaScript variables via the `CONFIG` object found in the `packages/components/src/utils/index.js` file: + +- `CONFIG.elevationXSmall` +- `CONFIG.elevationSmall` +- `CONFIG.elevationMedium` +- `CONFIG.elevationLarge` + +```js +// Note: the `CONFIG` object is only available within the `@wordpress/components` package. +import { CONFIG } from '../utils'; + +// Later in the code: +box-shadow: ${ CONFIG.elevationXSmall }; +``` From 3e2e434c55347584b25273006a03ce82677c3ee3 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 18 Oct 2024 13:51:36 +1100 Subject: [PATCH 14/30] Correct docs on display type in flow layouts. (#66224) --- docs/explanations/architecture/styles.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/explanations/architecture/styles.md b/docs/explanations/architecture/styles.md index 952c6c49caad23..68f09f04d21d32 100644 --- a/docs/explanations/architecture/styles.md +++ b/docs/explanations/architecture/styles.md @@ -510,7 +510,7 @@ When a block that opts in to layout support is rendered, two things are processe There are currently four layout types in use: -- Default/Flow: Items are stacked vertically. The parent container block is set to `display: flow` and the spacing between children is handled via vertical margins. +- Default/Flow: Items are stacked vertically. The parent container block's display value isn't specified, so that it may use the default value for that HTML element. For most elements that will usually be `block`. The spacing between children is handled via vertical margins. - Constrained: Items are stacked vertically, using the same spacing logic as the Flow layout. Features constrained widths for child content, outputting widths for standard content size and wide size. Defaults to using global `contentSize` and `wideSize` values set in `settings.layout` in the `theme.json`. - Flex: Items are displayed using a Flexbox layout. Defaults to a horizontal orientation. Spacing between children is handled via the `gap` CSS property. - Grid: Items are displayed using a Grid layout. Defaults to an `auto-fill` approach to column generation but can also be set to a fixed number of columns. Spacing between children is handled via the `gap` CSS property. From 8b4fd4c305a6effa8afbb37f46d76c34e40ddced Mon Sep 17 00:00:00 2001 From: Madhu Dollu Date: Fri, 18 Oct 2024 12:48:23 +0530 Subject: [PATCH 15/30] Fix zoom out not persisting while switching between editor and code editor (#65932) * reset zoomLevel on component unmount * conditionally reset zoom level * revert iframe changes and reset zoomLevel in the text-editor Co-authored-by: madhusudhand Co-authored-by: draganescu Co-authored-by: getdave Co-authored-by: youknowriad Co-authored-by: aaronrobertshaw Co-authored-by: yani- Co-authored-by: ironprogrammer Co-authored-by: simison Co-authored-by: PARTHVATALIYA --- packages/editor/src/components/text-editor/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/components/text-editor/index.js b/packages/editor/src/components/text-editor/index.js index fa0688859b5a69..6997c66826a12d 100644 --- a/packages/editor/src/components/text-editor/index.js +++ b/packages/editor/src/components/text-editor/index.js @@ -6,6 +6,7 @@ import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; import { useEffect, useRef } from '@wordpress/element'; +import { store as blockEditorStore } from '@wordpress/block-editor'; /** * Internal dependencies @@ -13,6 +14,7 @@ import { useEffect, useRef } from '@wordpress/element'; import { store as editorStore } from '../../store'; import PostTextEditor from '../post-text-editor'; import PostTitleRaw from '../post-title/post-title-raw'; +import { unlock } from '../../lock-unlock'; export default function TextEditor( { autoFocus = false } ) { const { switchEditorMode } = useDispatch( editorStore ); @@ -26,13 +28,20 @@ export default function TextEditor( { autoFocus = false } ) { }; }, [] ); + const { resetZoomLevel, __unstableSetEditorMode } = unlock( + useDispatch( blockEditorStore ) + ); + const titleRef = useRef(); useEffect( () => { + resetZoomLevel(); + __unstableSetEditorMode( 'edit' ); + if ( autoFocus ) { return; } titleRef?.current?.focus(); - }, [ autoFocus ] ); + }, [ autoFocus, resetZoomLevel, __unstableSetEditorMode ] ); return (
From d330f30a528a252c56ee4adbee2f8ee5f82a0312 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Fri, 18 Oct 2024 09:27:52 +0200 Subject: [PATCH 16/30] Global styles menu: Avoid visible labels and accessible names mismatch (#65124) * Simplify global styles root menu labels. * Fix and simplify labels in various global styles submenus. * Adjust failing test. * Fix more tests. * Fix one more test. * Entirely remove palette icons and move color randomizer. * Restore randomize colors position and add visible label. Co-authored-by: afercia Co-authored-by: richtabor Co-authored-by: ntsekouras Co-authored-by: t-hamano Co-authored-by: Mamaduka Co-authored-by: jameskoster Co-authored-by: ciampo --- .../components/global-styles/color-panel.js | 12 +----- .../font-sizes/font-sizes-count.js | 5 +-- .../src/components/global-styles/palette.js | 39 ++++++++++--------- .../src/components/global-styles/root-menu.js | 19 ++------- .../global-styles/screen-block-list.js | 7 ---- .../components/global-styles/screen-root.js | 15 ++----- .../components/global-styles/shadows-panel.js | 4 -- .../global-styles/typography-elements.js | 14 ++----- .../variations/variations-panel.js | 1 - test/e2e/specs/editor/blocks/buttons.spec.js | 12 +++--- .../editor/blocks/navigation-colors.spec.js | 6 +-- .../keep-styles-on-block-transforms.spec.js | 2 +- .../specs/site-editor/font-library.spec.js | 34 +++++----------- .../site-editor/global-styles-sidebar.spec.js | 6 +-- .../site-editor/push-to-global-styles.spec.js | 15 +++---- test/e2e/specs/site-editor/style-book.spec.js | 6 +-- .../site-editor/style-variations.spec.js | 22 +++++------ test/e2e/specs/site-editor/styles.spec.js | 4 +- .../user-global-styles-revisions.spec.js | 6 +-- 19 files changed, 81 insertions(+), 148 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/color-panel.js b/packages/block-editor/src/components/global-styles/color-panel.js index a55a7b331bd0dd..7c5257ae93bfaa 100644 --- a/packages/block-editor/src/components/global-styles/color-panel.js +++ b/packages/block-editor/src/components/global-styles/color-panel.js @@ -20,7 +20,7 @@ import { privateApis as componentsPrivateApis, } from '@wordpress/components'; import { useCallback } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -167,10 +167,7 @@ const LabeledColorIndicators = ( { indicators, label } ) => ( ) ) } - + { label } @@ -231,11 +228,6 @@ function ColorPanelDropdown( { { 'is-open': isOpen } ), 'aria-expanded': isOpen, - 'aria-label': sprintf( - /* translators: %s is the type of color property, e.g., "background" */ - __( 'Color %s styles' ), - label - ), }; return ( diff --git a/packages/edit-site/src/components/global-styles/font-sizes/font-sizes-count.js b/packages/edit-site/src/components/global-styles/font-sizes/font-sizes-count.js index c268648a134a74..fb23a38fe43bc2 100644 --- a/packages/edit-site/src/components/global-styles/font-sizes/font-sizes-count.js +++ b/packages/edit-site/src/components/global-styles/font-sizes/font-sizes-count.js @@ -23,10 +23,7 @@ function FontSizes() { { __( 'Font Sizes' ) } - + { __( 'Font size presets' ) } diff --git a/packages/edit-site/src/components/global-styles/palette.js b/packages/edit-site/src/components/global-styles/palette.js index 4afb9d88feb3e9..9b8c3f829d2426 100644 --- a/packages/edit-site/src/components/global-styles/palette.js +++ b/packages/edit-site/src/components/global-styles/palette.js @@ -54,32 +54,35 @@ function Palette( { name } ) { const screenPath = ! name ? '/colors/palette' : '/blocks/' + encodeURIComponent( name ) + '/colors/palette'; - const paletteButtonText = - colors.length > 0 ? __( 'Edit palette' ) : __( 'Add colors' ); return ( { __( 'Palette' ) } - + - { colors.length <= 0 && ( + { colors.length > 0 ? ( + <> + + { colors + .slice( 0, 5 ) + .map( ( { color }, index ) => ( + + + + ) ) } + + + { __( 'Edit palette' ) } + + + ) : ( { __( 'Add colors' ) } ) } - - { colors - .slice( 0, 5 ) - .map( ( { color }, index ) => ( - - - - ) ) } - diff --git a/packages/edit-site/src/components/global-styles/root-menu.js b/packages/edit-site/src/components/global-styles/root-menu.js index 183686bb52d825..d8e8f5794f9d03 100644 --- a/packages/edit-site/src/components/global-styles/root-menu.js +++ b/packages/edit-site/src/components/global-styles/root-menu.js @@ -48,17 +48,12 @@ function RootMenu() { { __( 'Typography' ) } ) } { hasColorPanel && ( - + { __( 'Colors' ) } ) } @@ -72,20 +67,12 @@ function RootMenu() { ) } { hasShadowPanel && ( - + { __( 'Shadows' ) } ) } { hasLayoutPanel && ( - + { __( 'Layout' ) } ) } diff --git a/packages/edit-site/src/components/global-styles/screen-block-list.js b/packages/edit-site/src/components/global-styles/screen-block-list.js index 51807ba79aa111..441c4168f814aa 100644 --- a/packages/edit-site/src/components/global-styles/screen-block-list.js +++ b/packages/edit-site/src/components/global-styles/screen-block-list.js @@ -86,16 +86,9 @@ function BlockMenuItem( { block } ) { return null; } - const navigationButtonLabel = sprintf( - // translators: %s: is the name of a block e.g., 'Image' or 'Table'. - __( '%s block styles' ), - block.title - ); - return ( diff --git a/packages/edit-site/src/components/global-styles/screen-root.js b/packages/edit-site/src/components/global-styles/screen-root.js index d5b29eca5549c5..ffa85b046ead71 100644 --- a/packages/edit-site/src/components/global-styles/screen-root.js +++ b/packages/edit-site/src/components/global-styles/screen-root.js @@ -67,10 +67,7 @@ function ScreenRoot() { { hasVariations && ( - + { __( 'Browse styles' ) } @@ -107,10 +104,7 @@ function ScreenRoot() { ) } - + { __( 'Blocks' ) } - + { __( 'Additional CSS' ) } diff --git a/packages/edit-site/src/components/global-styles/shadows-panel.js b/packages/edit-site/src/components/global-styles/shadows-panel.js index 18e15ef7dd6f28..43e0c063f492b8 100644 --- a/packages/edit-site/src/components/global-styles/shadows-panel.js +++ b/packages/edit-site/src/components/global-styles/shadows-panel.js @@ -135,10 +135,6 @@ function ShadowItem( { shadow, category } ) { return ( { shadow.name } diff --git a/packages/edit-site/src/components/global-styles/typography-elements.js b/packages/edit-site/src/components/global-styles/typography-elements.js index eaff3de05112b6..d087c8df041521 100644 --- a/packages/edit-site/src/components/global-styles/typography-elements.js +++ b/packages/edit-site/src/components/global-styles/typography-elements.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { __experimentalItemGroup as ItemGroup, __experimentalVStack as VStack, @@ -36,17 +36,8 @@ function ElementItem( { parentMenu, element, label } ) { const [ gradientValue ] = useGlobalStyle( prefix + 'color.gradient' ); const [ color ] = useGlobalStyle( prefix + 'color.text' ); - const navigationButtonLabel = sprintf( - // translators: %s: is a subset of Typography, e.g., 'text' or 'links'. - __( 'Typography %s styles' ), - label - ); - return ( - + diff --git a/packages/edit-site/src/components/global-styles/variations/variations-panel.js b/packages/edit-site/src/components/global-styles/variations/variations-panel.js index f98cc65e5c95b1..22f2f50c58e24f 100644 --- a/packages/edit-site/src/components/global-styles/variations/variations-panel.js +++ b/packages/edit-site/src/components/global-styles/variations/variations-panel.js @@ -55,7 +55,6 @@ export function VariationsPanel( { name } ) { '/variations/' + encodeURIComponent( style.name ) } - aria-label={ style.label } > { style.label } diff --git a/test/e2e/specs/editor/blocks/buttons.spec.js b/test/e2e/specs/editor/blocks/buttons.spec.js index 25bc2d6101024a..d6b0a0a15c4ea2 100644 --- a/test/e2e/specs/editor/blocks/buttons.spec.js +++ b/test/e2e/specs/editor/blocks/buttons.spec.js @@ -291,11 +291,11 @@ test.describe( 'Buttons', () => { `role=region[name="Editor settings"i] >> role=tab[name="Styles"i]` ); await page.click( - 'role=region[name="Editor settings"i] >> role=button[name="Color Text styles"i]' + 'role=region[name="Editor settings"i] >> role=button[name="Text"i]' ); await page.click( 'role=option[name="Color: Cyan bluish gray"i]' ); await page.click( - 'role=region[name="Editor settings"i] >> role=button[name="Color Background styles"i]' + 'role=region[name="Editor settings"i] >> role=button[name="Background"i]' ); await page.click( 'role=option[name="Color: Vivid red"i]' ); @@ -320,13 +320,13 @@ test.describe( 'Buttons', () => { `role=region[name="Editor settings"i] >> role=tab[name="Styles"i]` ); await page.click( - 'role=region[name="Editor settings"i] >> role=button[name="Color Text styles"i]' + 'role=region[name="Editor settings"i] >> role=button[name="Text"i]' ); await page.click( 'role=button[name="Custom color picker."i]' ); await page.fill( 'role=textbox[name="Hex color"i]', 'ff0000' ); await page.click( - 'role=region[name="Editor settings"i] >> role=button[name="Color Background styles"i]' + 'role=region[name="Editor settings"i] >> role=button[name="Background"i]' ); await page.click( 'role=button[name="Custom color picker."i]' ); await page.fill( 'role=textbox[name="Hex color"i]', '00ff00' ); @@ -355,7 +355,7 @@ test.describe( 'Buttons', () => { `role=region[name="Editor settings"i] >> role=tab[name="Styles"i]` ); await page.click( - 'role=region[name="Editor settings"i] >> role=button[name="Color Background styles"i]' + 'role=region[name="Editor settings"i] >> role=button[name="Background"i]' ); await page.click( 'role=tab[name="Gradient"i]' ); await page.click( 'role=option[name="Gradient: Purple to yellow"i]' ); @@ -384,7 +384,7 @@ test.describe( 'Buttons', () => { `role=region[name="Editor settings"i] >> role=tab[name="Styles"i]` ); await page.click( - 'role=region[name="Editor settings"i] >> role=button[name="Color Background styles"i]' + 'role=region[name="Editor settings"i] >> role=button[name="Background"i]' ); await page.click( 'role=tab[name="Gradient"i]' ); await page.click( diff --git a/test/e2e/specs/editor/blocks/navigation-colors.spec.js b/test/e2e/specs/editor/blocks/navigation-colors.spec.js index 1ddd4af8ab2e13..8692b184fb13f9 100644 --- a/test/e2e/specs/editor/blocks/navigation-colors.spec.js +++ b/test/e2e/specs/editor/blocks/navigation-colors.spec.js @@ -91,13 +91,13 @@ test.describe( 'Navigation colors', () => { // In the sidebar inspector we add a link color and link hover color to the group block. await editor.openDocumentSettingsSidebar(); await page.getByRole( 'tab', { name: 'Styles' } ).click(); - await page.getByRole( 'button', { name: 'Color Text styles' } ).click(); + await page.getByRole( 'button', { name: 'Text' } ).click(); await page .getByRole( 'option', { name: 'Color: White' } ) .click( { force: true } ); await page - .getByRole( 'button', { name: 'Color Background styles' } ) + .getByRole( 'button', { name: 'Background', exact: true } ) .click(); await page .getByRole( 'option', { name: 'Color: Black' } ) @@ -148,7 +148,7 @@ test.describe( 'Navigation colors', () => { .getByRole( 'menuitemcheckbox', { name: 'Show Link' } ) .click(); await page.getByRole( 'tab', { name: 'Styles' } ).click(); - await page.getByRole( 'button', { name: 'Color Link styles' } ).click(); + await page.getByRole( 'button', { name: 'Link', exact: true } ).click(); // rga(207, 46 ,46) is the color of the "vivid red" color preset. await page .getByRole( 'option', { name: 'Color: Vivid red' } ) diff --git a/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js b/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js index 57b958fdfc4b44..32e3def9154ed1 100644 --- a/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js +++ b/test/e2e/specs/editor/various/keep-styles-on-block-transforms.spec.js @@ -17,7 +17,7 @@ test.describe( 'Keep styles on block transforms', () => { .locator( 'role=button[name="Add default block"i]' ) .click(); await page.keyboard.type( '## Heading' ); - await page.click( 'role=button[name="Color Text styles"i]' ); + await page.click( 'role=button[name="Text"i]' ); await page.click( 'role=option[name="Color: Luminous vivid orange"i]' ); await page.click( 'role=button[name="Heading"i]' ); diff --git a/test/e2e/specs/site-editor/font-library.spec.js b/test/e2e/specs/site-editor/font-library.spec.js index 7271768206d1b6..7928ef8f71c534 100644 --- a/test/e2e/specs/site-editor/font-library.spec.js +++ b/test/e2e/specs/site-editor/font-library.spec.js @@ -24,9 +24,7 @@ test.describe( 'Font Library', () => { .getByRole( 'region', { name: 'Editor top bar' } ) .getByRole( 'button', { name: 'Styles' } ) .click(); - await page - .getByRole( 'button', { name: 'Typography Styles' } ) - .click(); + await page.getByRole( 'button', { name: 'Typography' } ).click(); await page .getByRole( 'button', { name: 'Add fonts', @@ -43,9 +41,7 @@ test.describe( 'Font Library', () => { .getByRole( 'region', { name: 'Editor top bar' } ) .getByRole( 'button', { name: 'Styles' } ) .click(); - await page - .getByRole( 'button', { name: 'Typography Styles' } ) - .click(); + await page.getByRole( 'button', { name: 'Typography' } ).click(); const manageFontsIcon = page.getByRole( 'button', { name: 'Manage fonts', } ); @@ -71,9 +67,7 @@ test.describe( 'Font Library', () => { .getByRole( 'region', { name: 'Editor top bar' } ) .getByRole( 'button', { name: 'Styles' } ) .click(); - await page - .getByRole( 'button', { name: 'Typography Styles' } ) - .click(); + await page.getByRole( 'button', { name: 'Typography' } ).click(); const manageFontsIcon = page.getByRole( 'button', { name: 'Manage fonts', } ); @@ -87,9 +81,7 @@ test.describe( 'Font Library', () => { .getByRole( 'region', { name: 'Editor top bar' } ) .getByRole( 'button', { name: 'Styles' } ) .click(); - await page - .getByRole( 'button', { name: 'Typography Styles' } ) - .click(); + await page.getByRole( 'button', { name: 'Typography' } ).click(); await page .getByRole( 'button', { name: 'Manage fonts', @@ -108,9 +100,7 @@ test.describe( 'Font Library', () => { .getByRole( 'region', { name: 'Editor top bar' } ) .getByRole( 'button', { name: 'Styles' } ) .click(); - await page - .getByRole( 'button', { name: 'Typography Styles' } ) - .click(); + await page.getByRole( 'button', { name: 'Typography' } ).click(); await page .getByRole( 'button', { name: 'Manage fonts', @@ -160,9 +150,7 @@ test.describe( 'Font Library', () => { .getByRole( 'region', { name: 'Editor top bar' } ) .getByRole( 'button', { name: 'Styles' } ) .click(); - await page - .getByRole( 'button', { name: 'Typography Styles' } ) - .click(); + await page.getByRole( 'button', { name: 'Typography' } ).click(); await page .getByRole( 'button', { name: 'Add fonts', @@ -196,7 +184,7 @@ test.describe( 'Font Library', () => { // Check CSS preset was created. await page.getByRole( 'button', { name: 'Close' } ).click(); await page - .getByRole( 'button', { name: 'Typography Headings styles' } ) + .getByRole( 'button', { name: 'Headings', exact: true } ) .click(); await page.getByLabel( 'Font' ).selectOption( 'Exo 2' ); await expect( @@ -252,9 +240,7 @@ test.describe( 'Font Library', () => { // Click "Back" button await page.getByRole( 'button', { name: 'Back' } ).click(); - await page - .getByRole( 'button', { name: 'Typography styles' } ) - .click(); + await page.getByRole( 'button', { name: 'Typography' } ).click(); // Click "Jost 2 variants" button await page @@ -286,9 +272,7 @@ test.describe( 'Font Library', () => { // Click "Back" button await page.getByRole( 'button', { name: 'Back' } ).click(); - await page - .getByRole( 'button', { name: 'Typography styles' } ) - .click(); + await page.getByRole( 'button', { name: 'Typography' } ).click(); // Click Cardo font-family. await page diff --git a/test/e2e/specs/site-editor/global-styles-sidebar.spec.js b/test/e2e/specs/site-editor/global-styles-sidebar.spec.js index 0b034def6a3063..7f1b818df4ce0a 100644 --- a/test/e2e/specs/site-editor/global-styles-sidebar.spec.js +++ b/test/e2e/specs/site-editor/global-styles-sidebar.spec.js @@ -28,7 +28,7 @@ test.describe( 'Global styles sidebar', () => { .click(); await page .getByRole( 'region', { name: 'Editor settings' } ) - .getByRole( 'button', { name: 'Blocks styles' } ) + .getByRole( 'button', { name: 'Blocks' } ) .click(); await page @@ -38,11 +38,11 @@ test.describe( 'Global styles sidebar', () => { // Matches both Heading and Table of Contents blocks. // The latter contains "heading" in its description. await expect( - page.getByRole( 'button', { name: 'Heading block styles' } ) + page.getByRole( 'button', { name: 'Heading', exact: true } ) ).toBeVisible(); await expect( page.getByRole( 'button', { - name: 'Table of Contents block styles', + name: 'Table of Contents', } ) ).toBeVisible(); } ); diff --git a/test/e2e/specs/site-editor/push-to-global-styles.spec.js b/test/e2e/specs/site-editor/push-to-global-styles.spec.js index 26224a83e27ebd..3e30f764811b1f 100644 --- a/test/e2e/specs/site-editor/push-to-global-styles.spec.js +++ b/test/e2e/specs/site-editor/push-to-global-styles.spec.js @@ -29,13 +29,14 @@ test.describe( 'Push to Global Styles button', () => { await page.keyboard.type( 'A heading' ); const topBar = page.getByRole( 'region', { name: 'Editor top bar' } ); + const settingsPanel = page.getByRole( 'region', { + name: 'Editor settings', + } ); // Navigate to Styles -> Blocks -> Heading -> Typography await topBar.getByRole( 'button', { name: 'Styles' } ).click(); - await page.getByRole( 'button', { name: 'Blocks styles' } ).click(); - await page - .getByRole( 'button', { name: 'Heading block styles' } ) - .click(); + await settingsPanel.getByRole( 'button', { name: 'Blocks' } ).click(); + await settingsPanel.getByRole( 'button', { name: 'Heading' } ).click(); // Headings should not have uppercase await expect( @@ -95,9 +96,9 @@ test.describe( 'Push to Global Styles button', () => { await page .getByRole( 'button', { name: 'Styles', exact: true } ) .click(); - await page.getByRole( 'button', { name: 'Blocks styles' } ).click(); - await page - .getByRole( 'button', { name: 'Heading block styles' } ) + await page.getByRole( 'button', { name: 'Blocks' } ).click(); + await settingsPanel + .getByRole( 'button', { name: 'Heading', exact: true } ) .click(); // Headings should now have uppercase diff --git a/test/e2e/specs/site-editor/style-book.spec.js b/test/e2e/specs/site-editor/style-book.spec.js index 9a34f30f82ff9c..c94049872edcf6 100644 --- a/test/e2e/specs/site-editor/style-book.spec.js +++ b/test/e2e/specs/site-editor/style-book.spec.js @@ -97,8 +97,8 @@ test.describe( 'Style Book', () => { test( 'should allow to return Global Styles root when example is clicked', async ( { page, } ) => { - await page.click( 'role=button[name="Blocks styles"]' ); - await page.click( 'role=button[name="Heading block styles"]' ); + await page.click( 'role=button[name="Blocks"]' ); + await page.click( 'role=button[name="Heading"]' ); await page .frameLocator( '[name="style-book-canvas"]' ) @@ -111,7 +111,7 @@ test.describe( 'Style Book', () => { await page.click( 'role=button[name="Back"]' ); await expect( - page.locator( 'role=button[name="Blocks styles"]' ) + page.locator( 'role=button[name="Blocks"]' ) ).toBeVisible(); } ); diff --git a/test/e2e/specs/site-editor/style-variations.spec.js b/test/e2e/specs/site-editor/style-variations.spec.js index c4dad7c7d7546c..9c4243b0d171f6 100644 --- a/test/e2e/specs/site-editor/style-variations.spec.js +++ b/test/e2e/specs/site-editor/style-variations.spec.js @@ -78,23 +78,23 @@ test.describe( 'Global styles variations', () => { await siteEditorStyleVariations.browseStyles(); await page.click( 'role=button[name="pink"i]' ); await page.click( 'role=button[name="Back"i]' ); - await page.click( 'role=button[name="Colors styles"i]' ); + await page.click( 'role=button[name="Colors"i]' ); await expect( page.locator( - 'role=button[name="Color Background styles"i] >> .component-color-indicator' + 'role=button[name="Background"i] >> .component-color-indicator' ) ).toHaveCSS( 'background', /rgb\(202, 105, 211\)/ ); await expect( page.locator( - 'role=button[name="Color Text styles"i] >> .component-color-indicator' + 'role=button[name="Text"i] >> .component-color-indicator' ) ).toHaveCSS( 'background', /rgb\(74, 7, 74\)/ ); await page.click( 'role=button[name="Back"i]' ); - await page.click( 'role=button[name="Typography styles"i]' ); - await page.click( 'role=button[name="Typography Text styles"i]' ); + await page.click( 'role=button[name="Typography"i]' ); + await page.click( 'role=button[name="Text"i]' ); await expect( page.locator( 'css=.components-font-size-picker__header__hint' ) @@ -114,23 +114,23 @@ test.describe( 'Global styles variations', () => { await siteEditorStyleVariations.browseStyles(); await page.click( 'role=button[name="yellow"i]' ); await page.click( 'role=button[name="Back"i]' ); - await page.click( 'role=button[name="Colors styles"i]' ); + await page.click( 'role=button[name="Colors"i]' ); await expect( page.locator( - 'role=button[name="Color Background styles"i] >> .component-color-indicator' + 'role=button[name="Background"i] >> .component-color-indicator' ) ).toHaveCSS( 'background', /rgb\(255, 239, 11\)/ ); await expect( page.locator( - 'role=button[name="Color Text styles"i] >> .component-color-indicator' + 'role=button[name="Text"i] >> .component-color-indicator' ) ).toHaveCSS( 'background', /rgb\(25, 25, 17\)/ ); await page.click( 'role=button[name="Back"i]' ); - await page.click( 'role=button[name="Typography styles"i]' ); - await page.click( 'role=button[name="Typography Text styles"i]' ); + await page.click( 'role=button[name="Typography"i]' ); + await page.click( 'role=button[name="Text"i]' ); // TODO: to avoid use classnames to locate these elements, // we could provide accessible attributes to the source code in packages/components/src/font-size-picker/index.js. @@ -156,7 +156,7 @@ test.describe( 'Global styles variations', () => { await siteEditorStyleVariations.browseStyles(); await page.click( 'role=button[name="pink"i]' ); await page.click( 'role=button[name="Back"i]' ); - await page.click( 'role=button[name="Colors styles"i]' ); + await page.click( 'role=button[name="Colors"i]' ); await page.click( 'role=button[name="Edit palette"i]' ); await expect( diff --git a/test/e2e/specs/site-editor/styles.spec.js b/test/e2e/specs/site-editor/styles.spec.js index 8da9aa95c67567..63a051bf6b92d7 100644 --- a/test/e2e/specs/site-editor/styles.spec.js +++ b/test/e2e/specs/site-editor/styles.spec.js @@ -40,9 +40,9 @@ test.describe( 'Styles', () => { const topBar = page.getByRole( 'region', { name: 'Editor top bar' } ); // Navigate to Styles -> Blocks -> Heading -> Typography await topBar.getByRole( 'button', { name: 'Styles' } ).click(); - await page.getByRole( 'button', { name: 'Blocks styles' } ).click(); + await page.getByRole( 'button', { name: 'Blocks' } ).click(); await page - .getByRole( 'button', { name: 'Social Icons block styles' } ) + .getByRole( 'button', { name: 'Social Icons', exact: true } ) .click(); // Find the second padding control and change the padding value diff --git a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js index f48c819c3a0891..e0b00fe1e59460 100644 --- a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js +++ b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js @@ -75,10 +75,8 @@ test.describe( 'Style Revisions', () => { } ) => { await editor.canvas.locator( 'body' ).click(); await userGlobalStylesRevisions.openStylesPanel(); - await page.getByRole( 'button', { name: 'Colors styles' } ).click(); - await page - .getByRole( 'button', { name: 'Color Background styles' } ) - .click(); + await page.getByRole( 'button', { name: 'Colors' } ).click(); + await page.getByRole( 'button', { name: 'Background' } ).click(); await page .getByRole( 'option', { name: 'Color: Luminous vivid amber' } ) .click( { force: true } ); From 388dd815225f6b880b180c75686ce4ec08bb4139 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 18 Oct 2024 19:10:53 +1100 Subject: [PATCH 17/30] Zoom Out: Disable zoom out toggle button when Style Book is open (#66228) Co-authored-by: andrewserong Co-authored-by: madhusudhand Co-authored-by: getdave Co-authored-by: colorful-tones --- packages/editor/src/components/header/index.js | 2 +- packages/editor/src/components/zoom-out-toggle/index.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/header/index.js b/packages/editor/src/components/header/index.js index 631643f26d4d5f..d3c82a65a5bc6f 100644 --- a/packages/editor/src/components/header/index.js +++ b/packages/editor/src/components/header/index.js @@ -149,7 +149,7 @@ function Header( { ) } { canBeZoomedOut && isEditorIframed && isWideViewport && ( - + ) } { +const ZoomOutToggle = ( { disabled } ) => { const { isZoomOut, showIconLabels } = useSelect( ( select ) => ( { isZoomOut: unlock( select( blockEditorStore ) ).isZoomOut(), showIconLabels: select( preferencesStore ).get( @@ -37,6 +37,8 @@ const ZoomOutToggle = () => { return (
) } @@ -188,7 +203,7 @@ function PostFeaturedImage( { ) } { isLoading && } From e2097c31041a7b21ddb6e264a4fc15072d462d85 Mon Sep 17 00:00:00 2001 From: Django Date: Fri, 18 Oct 2024 04:31:19 -0600 Subject: [PATCH 19/30] Remove clip & -webkit-clip-path for downloadable-block-list-item style.scss (#66147) * Remove clip for screen-reader-text CSS * item__author display none while is-installing Co-authored-by: mediaformat Co-authored-by: afercia Co-authored-by: colorful-tones --- .../downloadable-block-list-item/style.scss | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/block-directory/src/components/downloadable-block-list-item/style.scss b/packages/block-directory/src/components/downloadable-block-list-item/style.scss index 6fce5e1b5b32a7..e13e46ef8d8786 100644 --- a/packages/block-directory/src/components/downloadable-block-list-item/style.scss +++ b/packages/block-directory/src/components/downloadable-block-list-item/style.scss @@ -38,17 +38,7 @@ &.is-installing { .block-directory-downloadable-block-list-item__author { - border: 0; - clip: rect(1px, 1px, 1px, 1px); - -webkit-clip-path: inset(50%); - clip-path: inset(50%); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; - word-wrap: normal !important; + display: none; } } } From 6c0bca194715f0624be38a0df2cde3d5c970a881 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Fri, 18 Oct 2024 12:54:39 +0200 Subject: [PATCH 20/30] Interactivity API: Fix reactivity of undefined objects and arrays added with `deepMerge` (#66183) * Fix types in test * Add failing tests * Fix the bug * Update changelog * Fix changelog * Update tests Co-authored-by: DAreRodz Co-authored-by: michalczaplinski --- packages/interactivity/CHANGELOG.md | 4 ++ packages/interactivity/src/proxies/state.ts | 11 ++++- .../src/proxies/test/deep-merge.ts | 42 ++++++++++++++++++- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index b5a8fbcb9dde50..73212578ac109c 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fixes + +- Fix reactivity of undefined objects and arrays added with `deepMerge()` ([#66183](https://github.com/WordPress/gutenberg/pull/66183)). + ## 6.10.0 (2024-10-16) ### Internal diff --git a/packages/interactivity/src/proxies/state.ts b/packages/interactivity/src/proxies/state.ts index f63c84738bcb1a..d4d573acba8847 100644 --- a/packages/interactivity/src/proxies/state.ts +++ b/packages/interactivity/src/proxies/state.ts @@ -335,7 +335,10 @@ const deepMergeRecursive = ( if ( isNew || ( override && ! isPlainObject( target[ key ] ) ) ) { target[ key ] = {}; if ( propSignal ) { - propSignal.setValue( target[ key ] ); + const ns = getNamespaceFromProxy( proxy ); + propSignal.setValue( + proxifyState( ns, target[ key ] as Object ) + ); } } if ( isPlainObject( target[ key ] ) ) { @@ -344,7 +347,11 @@ const deepMergeRecursive = ( } else if ( override || isNew ) { Object.defineProperty( target, key, desc ); if ( propSignal ) { - propSignal.setValue( desc.value ); + const { value } = desc; + const ns = getNamespaceFromProxy( proxy ); + propSignal.setValue( + shouldProxy( value ) ? proxifyState( ns, value ) : value + ); } } } diff --git a/packages/interactivity/src/proxies/test/deep-merge.ts b/packages/interactivity/src/proxies/test/deep-merge.ts index f475385a437876..267e4850f9af91 100644 --- a/packages/interactivity/src/proxies/test/deep-merge.ts +++ b/packages/interactivity/src/proxies/test/deep-merge.ts @@ -379,7 +379,10 @@ describe( 'Interactivity API', () => { const target = proxifyState< any >( 'test', { a: 1, b: 2 } ); const source = { a: 1, b: 2, c: 3 }; - const spy = jest.fn( () => Object.keys( target ) ); + let keys: any; + const spy = jest.fn( () => { + keys = Object.keys( target ); + } ); effect( spy ); expect( spy ).toHaveBeenCalledTimes( 1 ); @@ -387,7 +390,7 @@ describe( 'Interactivity API', () => { deepMerge( target, source, false ); expect( spy ).toHaveBeenCalledTimes( 2 ); - expect( spy ).toHaveLastReturnedWith( [ 'a', 'b', 'c' ] ); + expect( keys ).toEqual( [ 'a', 'b', 'c' ] ); } ); it( 'should handle deeply nested properties that are initially undefined', () => { @@ -412,6 +415,13 @@ describe( 'Interactivity API', () => { // Reading the value directly should also work expect( target.a.b.c.d ).toBe( 'test value' ); + + // Modify the nested value + target.a.b.c.d = 'new test value'; + + // The effect should be called again + expect( spy ).toHaveBeenCalledTimes( 3 ); + expect( deepValue ).toBe( 'new test value' ); } ); it( 'should overwrite values that become objects', () => { @@ -462,5 +472,33 @@ describe( 'Interactivity API', () => { expect( target.message.content ).toBeUndefined(); expect( target.message.fontStyle ).toBeUndefined(); } ); + + it( 'should keep reactivity of arrays that are initially undefined', () => { + const target: any = proxifyState( 'test', {} ); + + let deepValue: any; + const spy = jest.fn( () => { + deepValue = target.array?.[ 0 ]; + } ); + effect( spy ); + + // Initial call, the deep value is undefined + expect( spy ).toHaveBeenCalledTimes( 1 ); + expect( deepValue ).toBeUndefined(); + + // Use deepMerge to add an array to the target + deepMerge( target, { array: [ 'value 1' ] } ); + + // The effect should be called again + expect( spy ).toHaveBeenCalledTimes( 2 ); + expect( deepValue ).toBe( 'value 1' ); + + // Modify the array value + target.array[ 0 ] = 'value 2'; + + // The effect should be called again + expect( spy ).toHaveBeenCalledTimes( 3 ); + expect( deepValue ).toBe( 'value 2' ); + } ); } ); } ); From 5ee948aec84d88e436af0814de3e7e6461ead3c3 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 18 Oct 2024 13:13:13 +0200 Subject: [PATCH 21/30] Tabs: restore vertical alignment for tabs content (#66215) Co-authored-by: ciampo Co-authored-by: tyxla --- .../src/components/block-inspector/style.scss | 6 ------ .../src/components/inspector-controls-tabs/index.js | 7 +------ .../src/components/inspector-controls-tabs/utils.js | 3 --- packages/components/CHANGELOG.md | 1 + packages/components/src/tabs/styles.ts | 12 ++++++++++-- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/block-editor/src/components/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss index 92a9a0dd03ab35..e04dfd8e9a480a 100644 --- a/packages/block-editor/src/components/block-inspector/style.scss +++ b/packages/block-editor/src/components/block-inspector/style.scss @@ -46,9 +46,3 @@ .block-editor-block-inspector__no-block-tools { border-top: $border-width solid $gray-300; } - -.block-editor-block-inspector__tab-item { - flex: 1 1 0px; - display: flex; - justify-content: center; -} diff --git a/packages/block-editor/src/components/inspector-controls-tabs/index.js b/packages/block-editor/src/components/inspector-controls-tabs/index.js index c8d9536aeb9caf..12465df0ca467c 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/index.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/index.js @@ -46,18 +46,13 @@ export default function InspectorControlsTabs( { { tabs.map( ( tab ) => showIconLabels ? ( - + { tab.title } ) : ( diff --git a/packages/block-editor/src/components/inspector-controls-tabs/utils.js b/packages/block-editor/src/components/inspector-controls-tabs/utils.js index b70eb25e274a9b..ff05e79cbd37cc 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/utils.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/utils.js @@ -9,7 +9,6 @@ export const TAB_SETTINGS = { title: __( 'Settings' ), value: 'settings', icon: cog, - className: 'block-editor-block-inspector__tab-item', }; export const TAB_STYLES = { @@ -17,7 +16,6 @@ export const TAB_STYLES = { title: __( 'Styles' ), value: 'styles', icon: styles, - className: 'block-editor-block-inspector__tab-item', }; export const TAB_LIST_VIEW = { @@ -25,5 +23,4 @@ export const TAB_LIST_VIEW = { title: __( 'List View' ), value: 'list-view', icon: listView, - className: 'block-editor-block-inspector__tab-item', }; diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 53ff933848338c..bd96eed8762b37 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -4,6 +4,7 @@ ### Bug Fixes +- `Tabs`: restore vertical alignent for tabs content ([#66215](https://github.com/WordPress/gutenberg/pull/66215)). - `Tabs`: fix indicator animation ([#66198](https://github.com/WordPress/gutenberg/pull/66198)). - `ColorPalette`: prevent overflow of custom color button background ([#66152](https://github.com/WordPress/gutenberg/pull/66152)). - `RadioGroup`: Fix arrow key navigation in RTL ([#66202](https://github.com/WordPress/gutenberg/pull/66202)). diff --git a/packages/components/src/tabs/styles.ts b/packages/components/src/tabs/styles.ts index 36e0ed6f6b0147..717316227ddb3c 100644 --- a/packages/components/src/tabs/styles.ts +++ b/packages/components/src/tabs/styles.ts @@ -207,7 +207,6 @@ export const Tab = styled( Ariakit.Tab )` [aria-orientation='horizontal'] & { padding-inline: ${ space( 4 ) }; height: ${ space( 12 ) }; - text-align: center; scroll-margin: 24px; &::after { @@ -219,7 +218,6 @@ export const Tab = styled( Ariakit.Tab )` [aria-orientation='vertical'] & { padding: ${ space( 2 ) } ${ space( 3 ) }; min-height: ${ space( 10 ) }; - text-align: start; &[aria-selected='true'] { color: ${ COLORS.theme.accent }; @@ -234,6 +232,16 @@ export const Tab = styled( Ariakit.Tab )` export const TabChildren = styled.span` flex-grow: 1; + + display: flex; + align-items: center; + + [aria-orientation='horizontal'] & { + justify-content: center; + } + [aria-orientation='vertical'] & { + justify-content: start; + } `; export const TabChevron = styled( Icon )` From 240180a808b59f03fd683128e4e7a431a9eaf9f9 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 18 Oct 2024 13:59:12 +0200 Subject: [PATCH 22/30] Tabs: align to standard compound components structure (#66225) Co-authored-by: ciampo Co-authored-by: tyxla --- packages/components/src/private-apis.ts | 2 +- packages/components/src/tabs/index.tsx | 355 +++++++++--------- .../src/tabs/stories/index.story.tsx | 4 +- packages/components/src/tabs/test/index.tsx | 2 +- 4 files changed, 191 insertions(+), 172 deletions(-) diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index b6e033ab24ab74..51f820251b8d7c 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -6,7 +6,7 @@ import { createPrivateSlotFill } from './slot-fill'; import { DropdownMenuV2 } from './dropdown-menu-v2'; import { ComponentsContext } from './context/context-system-provider'; import Theme from './theme'; -import Tabs from './tabs'; +import { Tabs } from './tabs'; import { kebabCase } from './utils/strings'; import { lock } from './lock-unlock'; diff --git a/packages/components/src/tabs/index.tsx b/packages/components/src/tabs/index.tsx index 09225c0cce9b8d..37367e4642e152 100644 --- a/packages/components/src/tabs/index.tsx +++ b/packages/components/src/tabs/index.tsx @@ -25,192 +25,209 @@ import { Tab } from './tab'; import { TabList } from './tablist'; import { TabPanel } from './tabpanel'; -function Tabs( { - selectOnMove = true, - defaultTabId, - orientation = 'horizontal', - onSelect, - children, - selectedTabId, -}: TabsProps ) { - const instanceId = useInstanceId( Tabs, 'tabs' ); - const store = Ariakit.useTabStore( { - selectOnMove, - orientation, - defaultSelectedId: defaultTabId && `${ instanceId }-${ defaultTabId }`, - setSelectedId: ( selectedId ) => { - const strippedDownId = - typeof selectedId === 'string' - ? selectedId.replace( `${ instanceId }-`, '' ) - : selectedId; - onSelect?.( strippedDownId ); - }, - selectedId: selectedTabId && `${ instanceId }-${ selectedTabId }`, - rtl: isRTL(), - } ); - - const isControlled = selectedTabId !== undefined; - - const { items, selectedId, activeId } = useStoreState( store ); - const { setSelectedId, setActiveId } = store; - - // Keep track of whether tabs have been populated. This is used to prevent - // certain effects from firing too early while tab data and relevant - // variables are undefined during the initial render. - const tabsHavePopulatedRef = useRef( false ); - if ( items.length > 0 ) { - tabsHavePopulatedRef.current = true; - } +/** + * Display one panel of content at a time with a tabbed interface, based on the + * WAI-ARIA Tabs Pattern⁠. + * + * @see https://www.w3.org/WAI/ARIA/apg/patterns/tabs/ + * ``` + */ +export const Tabs = Object.assign( + function Tabs( { + selectOnMove = true, + defaultTabId, + orientation = 'horizontal', + onSelect, + children, + selectedTabId, + }: TabsProps ) { + const instanceId = useInstanceId( Tabs, 'tabs' ); + const store = Ariakit.useTabStore( { + selectOnMove, + orientation, + defaultSelectedId: + defaultTabId && `${ instanceId }-${ defaultTabId }`, + setSelectedId: ( selectedId ) => { + const strippedDownId = + typeof selectedId === 'string' + ? selectedId.replace( `${ instanceId }-`, '' ) + : selectedId; + onSelect?.( strippedDownId ); + }, + selectedId: selectedTabId && `${ instanceId }-${ selectedTabId }`, + rtl: isRTL(), + } ); - const selectedTab = items.find( ( item ) => item.id === selectedId ); - const firstEnabledTab = items.find( ( item ) => { - // Ariakit internally refers to disabled tabs as `dimmed`. - return ! item.dimmed; - } ); - const initialTab = items.find( - ( item ) => item.id === `${ instanceId }-${ defaultTabId }` - ); - - // Handle selecting the initial tab. - useLayoutEffect( () => { - if ( isControlled ) { - return; - } + const isControlled = selectedTabId !== undefined; + + const { items, selectedId, activeId } = useStoreState( store ); + const { setSelectedId, setActiveId } = store; - // Wait for the denoted initial tab to be declared before making a - // selection. This ensures that if a tab is declared lazily it can - // still receive initial selection, as well as ensuring no tab is - // selected if an invalid `defaultTabId` is provided. - if ( defaultTabId && ! initialTab ) { - return; + // Keep track of whether tabs have been populated. This is used to prevent + // certain effects from firing too early while tab data and relevant + // variables are undefined during the initial render. + const tabsHavePopulatedRef = useRef( false ); + if ( items.length > 0 ) { + tabsHavePopulatedRef.current = true; } - // If the currently selected tab is missing (i.e. removed from the DOM), - // fall back to the initial tab or the first enabled tab if there is - // one. Otherwise, no tab should be selected. - if ( ! items.find( ( item ) => item.id === selectedId ) ) { - if ( initialTab && ! initialTab.dimmed ) { - setSelectedId( initialTab?.id ); + const selectedTab = items.find( ( item ) => item.id === selectedId ); + const firstEnabledTab = items.find( ( item ) => { + // Ariakit internally refers to disabled tabs as `dimmed`. + return ! item.dimmed; + } ); + const initialTab = items.find( + ( item ) => item.id === `${ instanceId }-${ defaultTabId }` + ); + + // Handle selecting the initial tab. + useLayoutEffect( () => { + if ( isControlled ) { return; } - if ( firstEnabledTab ) { - setSelectedId( firstEnabledTab.id ); - } else if ( tabsHavePopulatedRef.current ) { - setSelectedId( null ); + // Wait for the denoted initial tab to be declared before making a + // selection. This ensures that if a tab is declared lazily it can + // still receive initial selection, as well as ensuring no tab is + // selected if an invalid `defaultTabId` is provided. + if ( defaultTabId && ! initialTab ) { + return; } - } - }, [ - firstEnabledTab, - initialTab, - defaultTabId, - isControlled, - items, - selectedId, - setSelectedId, - ] ); - - // Handle the currently selected tab becoming disabled. - useLayoutEffect( () => { - if ( ! selectedTab?.dimmed ) { - return; - } - - // In controlled mode, we trust that disabling tabs is done - // intentionally, and don't select a new tab automatically. - if ( isControlled ) { - setSelectedId( null ); - return; - } - // If the currently selected tab becomes disabled, fall back to the - // `defaultTabId` if possible. Otherwise select the first - // enabled tab (if there is one). - if ( initialTab && ! initialTab.dimmed ) { - setSelectedId( initialTab.id ); - return; - } - - if ( firstEnabledTab ) { - setSelectedId( firstEnabledTab.id ); - } - }, [ - firstEnabledTab, - initialTab, - isControlled, - selectedTab?.dimmed, - setSelectedId, - ] ); - - // Clear `selectedId` if the active tab is removed from the DOM in controlled mode. - useLayoutEffect( () => { - if ( ! isControlled ) { - return; - } - - // Once the tabs have populated, if the `selectedTabId` still can't be - // found, clear the selection. - if ( - tabsHavePopulatedRef.current && - !! selectedTabId && - ! selectedTab - ) { - setSelectedId( null ); - } - }, [ isControlled, selectedTab, selectedTabId, setSelectedId ] ); + // If the currently selected tab is missing (i.e. removed from the DOM), + // fall back to the initial tab or the first enabled tab if there is + // one. Otherwise, no tab should be selected. + if ( ! items.find( ( item ) => item.id === selectedId ) ) { + if ( initialTab && ! initialTab.dimmed ) { + setSelectedId( initialTab?.id ); + return; + } + + if ( firstEnabledTab ) { + setSelectedId( firstEnabledTab.id ); + } else if ( tabsHavePopulatedRef.current ) { + setSelectedId( null ); + } + } + }, [ + firstEnabledTab, + initialTab, + defaultTabId, + isControlled, + items, + selectedId, + setSelectedId, + ] ); + + // Handle the currently selected tab becoming disabled. + useLayoutEffect( () => { + if ( ! selectedTab?.dimmed ) { + return; + } - useEffect( () => { - // If there is no active tab, fallback to place focus on the first enabled tab - // so there is always an active element - if ( selectedTabId === null && ! activeId && firstEnabledTab?.id ) { - setActiveId( firstEnabledTab.id ); - } - }, [ selectedTabId, activeId, firstEnabledTab?.id, setActiveId ] ); + // In controlled mode, we trust that disabling tabs is done + // intentionally, and don't select a new tab automatically. + if ( isControlled ) { + setSelectedId( null ); + return; + } - useEffect( () => { - if ( ! isControlled ) { - return; - } + // If the currently selected tab becomes disabled, fall back to the + // `defaultTabId` if possible. Otherwise select the first + // enabled tab (if there is one). + if ( initialTab && ! initialTab.dimmed ) { + setSelectedId( initialTab.id ); + return; + } - requestAnimationFrame( () => { - const focusedElement = - items?.[ 0 ]?.element?.ownerDocument.activeElement; + if ( firstEnabledTab ) { + setSelectedId( firstEnabledTab.id ); + } + }, [ + firstEnabledTab, + initialTab, + isControlled, + selectedTab?.dimmed, + setSelectedId, + ] ); + + // Clear `selectedId` if the active tab is removed from the DOM in controlled mode. + useLayoutEffect( () => { + if ( ! isControlled ) { + return; + } + // Once the tabs have populated, if the `selectedTabId` still can't be + // found, clear the selection. if ( - ! focusedElement || - ! items.some( ( item ) => focusedElement === item.element ) + tabsHavePopulatedRef.current && + !! selectedTabId && + ! selectedTab ) { - return; // Return early if no tabs are focused. + setSelectedId( null ); } + }, [ isControlled, selectedTab, selectedTabId, setSelectedId ] ); - // If, after ariakit re-computes the active tab, that tab doesn't match - // the currently focused tab, then we force an update to ariakit to avoid - // any mismatches, especially when navigating to previous/next tab with - // arrow keys. - if ( activeId !== focusedElement.id ) { - setActiveId( focusedElement.id ); + useEffect( () => { + // If there is no active tab, fallback to place focus on the first enabled tab + // so there is always an active element + if ( selectedTabId === null && ! activeId && firstEnabledTab?.id ) { + setActiveId( firstEnabledTab.id ); + } + }, [ selectedTabId, activeId, firstEnabledTab?.id, setActiveId ] ); + + useEffect( () => { + if ( ! isControlled ) { + return; } - } ); - }, [ activeId, isControlled, items, setActiveId ] ); - const contextValue = useMemo( - () => ( { - store, - instanceId, + requestAnimationFrame( () => { + const focusedElement = + items?.[ 0 ]?.element?.ownerDocument.activeElement; + + if ( + ! focusedElement || + ! items.some( ( item ) => focusedElement === item.element ) + ) { + return; // Return early if no tabs are focused. + } + + // If, after ariakit re-computes the active tab, that tab doesn't match + // the currently focused tab, then we force an update to ariakit to avoid + // any mismatches, especially when navigating to previous/next tab with + // arrow keys. + if ( activeId !== focusedElement.id ) { + setActiveId( focusedElement.id ); + } + } ); + }, [ activeId, isControlled, items, setActiveId ] ); + + const contextValue = useMemo( + () => ( { + store, + instanceId, + } ), + [ store, instanceId ] + ); + + return ( + + { children } + + ); + }, + { + Tab: Object.assign( Tab, { + displayName: 'Tabs.Tab', + } ), + TabList: Object.assign( TabList, { + displayName: 'Tabs.TabList', } ), - [ store, instanceId ] - ); - - return ( - - { children } - - ); -} - -Tabs.TabList = TabList; -Tabs.Tab = Tab; -Tabs.TabPanel = TabPanel; -Tabs.Context = TabsContext; - -export default Tabs; + TabPanel: Object.assign( TabPanel, { + displayName: 'Tabs.TabPanel', + } ), + Context: Object.assign( TabsContext, { + displayName: 'Tabs.Context', + } ), + } +); diff --git a/packages/components/src/tabs/stories/index.story.tsx b/packages/components/src/tabs/stories/index.story.tsx index 5141a48a899502..5b2fd621bbb436 100644 --- a/packages/components/src/tabs/stories/index.story.tsx +++ b/packages/components/src/tabs/stories/index.story.tsx @@ -12,7 +12,7 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Tabs from '..'; +import { Tabs } from '..'; import { Slot, Fill, Provider as SlotFillProvider } from '../../slot-fill'; import DropdownMenu from '../../dropdown-menu'; import Button from '../../button'; @@ -30,6 +30,8 @@ const meta: Meta< typeof Tabs > = { 'Tabs.Tab': Tabs.Tab, // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 'Tabs.TabPanel': Tabs.TabPanel, + // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 + 'Tabs.Context': Tabs.Context, }, tags: [ 'status-private' ], parameters: { diff --git a/packages/components/src/tabs/test/index.tsx b/packages/components/src/tabs/test/index.tsx index 0563b0227d106e..82c75a3f16b253 100644 --- a/packages/components/src/tabs/test/index.tsx +++ b/packages/components/src/tabs/test/index.tsx @@ -13,7 +13,7 @@ import { useEffect, useState } from '@wordpress/element'; /** * Internal dependencies */ -import Tabs from '..'; +import { Tabs } from '..'; import type { TabsProps } from '../types'; type Tab = { From fa120809525a00ee4cd49c88e15f4520fc9c9d04 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 18 Oct 2024 14:17:06 +0200 Subject: [PATCH 23/30] Tabs: update indicator more reactively (#66207) Co-authored-by: ciampo Co-authored-by: tyxla Co-authored-by: DaniGuardiola --- packages/components/CHANGELOG.md | 1 + packages/components/src/tabs/tablist.tsx | 32 ++++++++++++------- packages/components/src/utils/element-rect.ts | 17 ++++++++-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index bd96eed8762b37..9b3c31814fc59a 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -10,6 +10,7 @@ - `RadioGroup`: Fix arrow key navigation in RTL ([#66202](https://github.com/WordPress/gutenberg/pull/66202)). - `Tabs` and `TabPanel`: Fix arrow key navigation in RTL ([#66201](https://github.com/WordPress/gutenberg/pull/66201)). - `Tabs`: override tablist's tabindex only when necessary ([#66209](https://github.com/WordPress/gutenberg/pull/66209)). +- `Tabs`: update indicator more reactively ([#66207](https://github.com/WordPress/gutenberg/pull/66207)). ### Enhancements diff --git a/packages/components/src/tabs/tablist.tsx b/packages/components/src/tabs/tablist.tsx index 998da5707aa071..181c705e7148cf 100644 --- a/packages/components/src/tabs/tablist.tsx +++ b/packages/components/src/tabs/tablist.tsx @@ -1,7 +1,8 @@ /** * External dependencies */ -import { useStoreState } from '@ariakit/react'; +import * as Ariakit from '@ariakit/react'; +import clsx from 'clsx'; /** * WordPress dependencies @@ -14,11 +15,10 @@ import { useMergeRefs } from '@wordpress/compose'; * Internal dependencies */ import type { TabListProps } from './types'; -import { useTabsContext } from './context'; -import { StyledTabList } from './styles'; import type { WordPressComponentProps } from '../context'; -import clsx from 'clsx'; import type { ElementOffsetRect } from '../utils/element-rect'; +import { useTabsContext } from './context'; +import { StyledTabList } from './styles'; import { useTrackElementOffsetRect } from '../utils/element-rect'; import { useTrackOverflow } from './use-track-overflow'; import { useAnimatedOffsetRect } from '../utils/hooks/use-animated-offset-rect'; @@ -62,15 +62,25 @@ export const TabList = forwardRef< >( function TabList( { children, ...otherProps }, ref ) { const { store } = useTabsContext() ?? {}; - const selectedId = useStoreState( store, 'selectedId' ); - const activeId = useStoreState( store, 'activeId' ); - const selectOnMove = useStoreState( store, 'selectOnMove' ); - const items = useStoreState( store, 'items' ); + const selectedId = Ariakit.useStoreState( store, 'selectedId' ); + const activeId = Ariakit.useStoreState( store, 'activeId' ); + const selectOnMove = Ariakit.useStoreState( store, 'selectOnMove' ); + const items = Ariakit.useStoreState( store, 'items' ); const [ parent, setParent ] = useState< HTMLElement >(); const refs = useMergeRefs( [ ref, setParent ] ); - const selectedRect = useTrackElementOffsetRect( - store?.item( selectedId )?.element - ); + + const selectedItem = store?.item( selectedId ); + const renderedItems = Ariakit.useStoreState( store, 'renderedItems' ); + + const selectedItemIndex = + renderedItems && selectedItem + ? renderedItems.indexOf( selectedItem ) + : -1; + // Use selectedItemIndex as a dependency to force recalculation when the + // selected item index changes (elements are swapped / added / removed). + const selectedRect = useTrackElementOffsetRect( selectedItem?.element, [ + selectedItemIndex, + ] ); // Track overflow to show scroll hints. const overflow = useTrackOverflow( parent, { diff --git a/packages/components/src/utils/element-rect.ts b/packages/components/src/utils/element-rect.ts index 7c83db4428ca0f..7f9693ef9f7df2 100644 --- a/packages/components/src/utils/element-rect.ts +++ b/packages/components/src/utils/element-rect.ts @@ -134,14 +134,17 @@ const POLL_RATE = 100; * milliseconds until it succeeds. */ export function useTrackElementOffsetRect( - targetElement: HTMLElement | undefined | null + targetElement: HTMLElement | undefined | null, + deps: unknown[] = [] ) { const [ indicatorPosition, setIndicatorPosition ] = useState< ElementOffsetRect >( NULL_ELEMENT_OFFSET_RECT ); const intervalRef = useRef< ReturnType< typeof setInterval > >(); const measure = useEvent( () => { - if ( targetElement ) { + // Check that the targetElement is still attached to the DOM, in case + // it was removed since the last `measure` call. + if ( targetElement && targetElement.isConnected ) { const elementOffsetRect = getElementOffsetRect( targetElement ); if ( elementOffsetRect ) { setIndicatorPosition( elementOffsetRect ); @@ -171,6 +174,16 @@ export function useTrackElementOffsetRect( } }, [ setElement, targetElement ] ); + // Escape hatch to force a remeasurement when something else changes rather + // than the target elements' ref or size (for example, the target element + // can change its position within the tablist). + useLayoutEffect( () => { + measure(); + // `measure` is a stable function, so it's safe to omit it from the deps array. + // deps can't be statically analyzed by ESLint + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps ); + return indicatorPosition; } From 220547e332c1e3d4a4e123b15ddcb40fc3420f8d Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 18 Oct 2024 14:54:12 +0200 Subject: [PATCH 24/30] Tabs: expose active tab item props, use ariakit prop types (#66223) Co-authored-by: ciampo Co-authored-by: tyxla --- packages/components/CHANGELOG.md | 14 ++- packages/components/src/tabs/README.md | 84 +++++++++++---- packages/components/src/tabs/types.ts | 138 ++++++++++++++++--------- 3 files changed, 161 insertions(+), 75 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 9b3c31814fc59a..165ce7e3748557 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -4,18 +4,22 @@ ### Bug Fixes -- `Tabs`: restore vertical alignent for tabs content ([#66215](https://github.com/WordPress/gutenberg/pull/66215)). -- `Tabs`: fix indicator animation ([#66198](https://github.com/WordPress/gutenberg/pull/66198)). - `ColorPalette`: prevent overflow of custom color button background ([#66152](https://github.com/WordPress/gutenberg/pull/66152)). - `RadioGroup`: Fix arrow key navigation in RTL ([#66202](https://github.com/WordPress/gutenberg/pull/66202)). -- `Tabs` and `TabPanel`: Fix arrow key navigation in RTL ([#66201](https://github.com/WordPress/gutenberg/pull/66201)). -- `Tabs`: override tablist's tabindex only when necessary ([#66209](https://github.com/WordPress/gutenberg/pull/66209)). -- `Tabs`: update indicator more reactively ([#66207](https://github.com/WordPress/gutenberg/pull/66207)). ### Enhancements - `PaletteEdit`: use `Item` internally instead of custom styles ([#66164](https://github.com/WordPress/gutenberg/pull/66164)). +### Experimental + +- `Tabs`: add props to control active tab item ([#66223](https://github.com/WordPress/gutenberg/pull/66223)). +- `Tabs`: restore vertical alignent for tabs content ([#66215](https://github.com/WordPress/gutenberg/pull/66215)). +- `Tabs`: fix indicator animation ([#66198](https://github.com/WordPress/gutenberg/pull/66198)). +- `Tabs`: update indicator more reactively ([#66207](https://github.com/WordPress/gutenberg/pull/66207)). +- `Tabs` and `TabPanel`: Fix arrow key navigation in RTL ([#66201](https://github.com/WordPress/gutenberg/pull/66201)). +- `Tabs`: override tablist's tabindex only when necessary ([#66209](https://github.com/WordPress/gutenberg/pull/66209)). + ## 28.10.0 (2024-10-16) ### Bug Fixes diff --git a/packages/components/src/tabs/README.md b/packages/components/src/tabs/README.md index 6eaadc0ae0b19d..9c7e846046c904 100644 --- a/packages/components/src/tabs/README.md +++ b/packages/components/src/tabs/README.md @@ -109,45 +109,81 @@ Tabs is comprised of four individual components: ###### `children`: `React.ReactNode` -The children elements, which should be at least a `Tabs.Tablist` component and a series of `Tabs.TabPanel` components. +The children elements, which should include one instance of the `Tabs.Tablist` component and as many instances of the `Tabs.TabPanel` components as there are `Tabs.Tab` components. - Required: Yes ###### `selectOnMove`: `boolean` -When `true`, the tab will be selected when receiving focus (automatic tab activation). When `false`, the tab will be selected only when clicked (manual tab activation). See the [official W3C docs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/) for more info. +Determines if the tab should be selected when it receives focus. If set to `false`, the tab will only be selected upon clicking, not when using arrow keys to shift focus (manual tab activation). See the [official W3C docs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/) for more info. - Required: No - Default: `true` -###### `defaultTabId`: `string` +###### `selectedTabId`: `string | null` + +The id of the tab whose panel is currently visible. + +If left `undefined`, it will be automatically set to the first enabled tab, and the component assumes it is being used in "uncontrolled" mode. + +Consequently, any value different than `undefined` will set the component in "controlled" mode. When in "controlled" mode, the `null` value will result in no tabs being selected, and the tablist becoming tabbable. + +- Required: No + +###### `defaultTabId`: `string | null` -The id of the tab to be selected upon mounting of component. If this prop is not set, the first tab will be selected by default. The id provided will be internally prefixed with a unique instance ID to avoid collisions. +The id of the tab whose panel is currently visible. -_Note: this prop will be overridden by the `selectedTabId` prop if it is provided. (Controlled Mode)_ +If left `undefined`, it will be automatically set to the first enabled tab. If set to `null`, no tab will be selected, and the tablist will be tabbable. + +_Note: this prop will be overridden by the `selectedTabId` prop if it is provided (meaning the component will be used in "controlled" mode)._ - Required: No ###### `onSelect`: `( ( selectedId: string | null | undefined ) => void )` -The function called when a tab has been selected. It is passed the selected tab's ID as an argument. +The function called when the `selectedTabId` changes. - Required: No - Default: `noop` -###### `orientation`: `horizontal | vertical` +###### `activeTabId`: `string | null` + +The current active tab `id`. The active tab is the tab element within the tablist widget that has DOM focus. + +- `null` represents the tablist (ie. the base composite element). Users + will be able to navigate out of it using arrow keys; +- If `activeTabId` is initially set to `null`, the base composite element + itself will have focus and users will be able to navigate to it using + arrow keys. + +- Required: No + +###### `defaultActiveTabId`: `string | null` + +The tab id that should be active by default when the composite widget is rendered. If `null`, the tablist element itself will have focus and users will be able to navigate to it using arrow keys. If `undefined`, the first enabled item will be focused. -The orientation of the `tablist` (`vertical` or `horizontal`) +_Note: this prop will be overridden by the `activeTabId` prop if it is provided._ - Required: No -- Default: `horizontal` -###### `selectedTabId`: `string | null` +###### `onActiveTabIdChange`: `( ( activeId: string | null | undefined ) => void )` -The ID of the tab to display. This id is prepended with the `Tabs` instanceId internally. -If left `undefined`, the component assumes it is being used in uncontrolled mode. Consequently, any value different than `undefined` will set the component in `controlled` mode. When in controlled mode, the `null` value will result in no tab being selected. +The function called when the `selectedTabId` changes. -- Required: No +- Required: No +- Default: `noop` + +###### `orientation`: `'horizontal' | 'vertical' | 'both'` + +Defines the orientation of the tablist and determines which arrow keys can be used to move focus: + +- `both`: all arrow keys work; +- `horizontal`: only left and right arrow keys work; +- `vertical`: only up and down arrow keys work. + +- Required: No +- Default: `horizontal` #### TabList @@ -155,7 +191,7 @@ If left `undefined`, the component assumes it is being used in uncontrolled mode ###### `children`: `React.ReactNode` -The children elements, which should be a series of `Tabs.TabPanel` components. +The children elements, which should include one or more instances of the `Tabs.Tab` component. - Required: No @@ -165,26 +201,28 @@ The children elements, which should be a series of `Tabs.TabPanel` components. ###### `tabId`: `string` -A unique identifier for the tab, which is used to generate a unique id for the underlying element. The value of this prop should match with the value of the `tabId` prop on the corresponding `Tabs.TabPanel` component. +The unique ID of the tab. It will be used to register the tab and match it to a corresponding `Tabs.TabPanel` component. If not provided, a unique ID will be automatically generated. - Required: Yes ###### `children`: `React.ReactNode` -The children elements, generally the text to display on the tab. +The contents of the tab. - Required: No ###### `disabled`: `boolean` -Determines if the tab button should be disabled. +Determines if the tab should be disabled. Note that disabled tabs can still be accessed via the keyboard when navigating through the tablist. - Required: No - Default: `false` ###### `render`: `React.ReactNode` -The type of component to render the tab button as. If this prop is not provided, the tab button will be rendered as a `button` element. +Allows the component to be rendered as a different HTML element or React component. The value can be a React element or a function that takes in the original component props and gives back a React element with the props merged. + +By default, the tab will be rendered as a `button` element. - Required: No @@ -194,19 +232,23 @@ The type of component to render the tab button as. If this prop is not provided, ###### `children`: `React.ReactNode` -The children elements, generally the content to display on the tabpanel. +The contents of the tab panel. - Required: No ###### `tabId`: `string` -A unique identifier for the tabpanel, which is used to generate an instanced id for the underlying element. The value of this prop should match with the value of the `tabId` prop on the corresponding `Tabs.Tab` component. +The unique `id` of the `Tabs.Tab` component controlling this panel. This connection is used to assign the `aria-labelledby` attribute to the tab panel and to determine if the tab panel should be visible. + +If not provided, this link is automatically established by matching the order of `Tabs.Tab` and `Tabs.TabPanel` elements in the DOM. - Required: Yes ###### `focusable`: `boolean` -Determines whether or not the tabpanel element should be focusable. If `false`, pressing the tab key will skip over the tabpanel, and instead focus on the first focusable element in the panel (if there is one). +Determines whether or not the tabpanel element should be focusable. + +If `false`, pressing the tab key will skip over the tabpanel, and instead focus on the first focusable element in the panel (if there is one). - Required: No - Default: `true` diff --git a/packages/components/src/tabs/types.ts b/packages/components/src/tabs/types.ts index 29baf25f224f7a..959a82509a05d6 100644 --- a/packages/components/src/tabs/types.ts +++ b/packages/components/src/tabs/types.ts @@ -18,98 +18,138 @@ export type TabsContextProps = export type TabsProps = { /** - * The children elements, which should be at least a - * `Tabs.Tablist` component and a series of `Tabs.TabPanel` - * components. + * The children elements, which should include one instance of the + * `Tabs.Tablist` component and as many instances of the `Tabs.TabPanel` + * components as there are `Tabs.Tab` components. */ - children: React.ReactNode; + children: Ariakit.TabProps[ 'children' ]; /** - * When `true`, the tab will be selected when receiving focus (automatic tab - * activation). When `false`, the tab will be selected only when clicked - * (manual tab activation). See the official W3C docs for more info. + * Determines if the tab should be selected when it receives focus. If set to + * `false`, the tab will only be selected upon clicking, not when using arrow + * keys to shift focus (manual tab activation). See the official W3C docs + * for more info. * * @default true * * @see https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/ */ - selectOnMove?: boolean; + selectOnMove?: Ariakit.TabStoreProps[ 'selectOnMove' ]; /** - * The id of the tab to be selected upon mounting of component. - * If this prop is not set, the first tab will be selected by default. - * The id provided will be internally prefixed with the - * `TabsContextProps.instanceId`. + * The id of the tab whose panel is currently visible. + * + * If left `undefined`, it will be automatically set to the first enabled + * tab, and the component assumes it is being used in "uncontrolled" mode. + * + * Consequently, any value different than `undefined` will set the component + * in "controlled" mode. When in "controlled" mode, the `null` value will + * result in no tabs being selected, and the tablist becoming tabbable. + */ + selectedTabId?: Ariakit.TabStoreProps[ 'selectedId' ]; + /** + * The id of the tab whose panel is currently visible. + * + * If left `undefined`, it will be automatically set to the first enabled + * tab. If set to `null`, no tab will be selected, and the tablist will be + * tabbable. * * Note: this prop will be overridden by the `selectedTabId` prop if it is - * provided. (Controlled Mode) + * provided (meaning the component will be used in "controlled" mode). */ - defaultTabId?: string; + defaultTabId?: Ariakit.TabStoreProps[ 'defaultSelectedId' ]; /** - * The function called when a tab has been selected. - * It is passed the id of the newly selected tab as an argument. + * The function called when the `selectedTabId` changes. */ - onSelect?: ( selectedId: string | null | undefined ) => void; - + onSelect?: Ariakit.TabStoreProps[ 'setSelectedId' ]; /** - * The orientation of the tablist. + * The current active tab `id`. The active tab is the tab element within the + * tablist widget that has DOM focus. + * - `null` represents the tablist (ie. the base composite element). Users + * will be able to navigate out of it using arrow keys. + * - If `activeTabId` is initially set to `null`, the base composite element + * itself will have focus and users will be able to navigate to it using + * arrow keys.activeTabId + */ + activeTabId?: Ariakit.TabStoreProps[ 'activeId' ]; + /** + * The tab id that should be active by default when the composite widget is + * rendered. If `null`, the tablist element itself will have focus + * and users will be able to navigate to it using arrow keys. If `undefined`, + * the first enabled item will be focused. * - * @default `horizontal` + * Note: this prop will be overridden by the `activeTabId` prop if it is + * provided. + */ + defaultActiveTabId?: Ariakit.TabStoreProps[ 'defaultActiveId' ]; + /** + * A callback that gets called when the `activeTabId` state changes. */ - orientation?: 'horizontal' | 'vertical'; + onActiveTabIdChange?: Ariakit.TabStoreProps[ 'setActiveId' ]; /** - * The Id of the tab to display. This id is prepended with the `Tabs` - * instanceId internally. + * Defines the orientation of the tablist and determines which arrow keys + * can be used to move focus: + * - `both`: all arrow keys work. + * - `horizontal`: only left and right arrow keys work. + * - `vertical`: only up and down arrow keys work. * - * If left `undefined`, the component assumes it is being used in - * uncontrolled mode. Consequently, any value different than `undefined` - * will set the component in `controlled` mode. - * When in controlled mode, the `null` value will result in no tab being selected. + * @default "horizontal" */ - selectedTabId?: string | null; + orientation?: Ariakit.TabStoreProps[ 'orientation' ]; }; export type TabListProps = { /** - * The children elements, which should be a series of `Tabs.TabPanel` components. + * The children elements, which should include one or more instances of the + * `Tabs.Tab` component. */ - children?: React.ReactNode; + children: Ariakit.TabListProps[ 'children' ]; }; +// TODO: consider prop name changes (tabId, selectedTabId) +// switch to auto-generated README +// compound technique + export type TabProps = { /** - * The id of the tab, which is prepended with the `Tabs` instanceId. - * The value of this prop should match with the value of the `tabId` prop on - * the corresponding `Tabs.TabPanel` component. + * The unique ID of the tab. It will be used to register the tab and match + * it to a corresponding `Tabs.TabPanel` component. */ - tabId: string; + tabId: NonNullable< Ariakit.TabProps[ 'id' ] >; /** - * The children elements, generally the text to display on the tab. + * The contents of the tab. */ - children?: React.ReactNode; + children?: Ariakit.TabProps[ 'children' ]; /** - * Determines if the tab button should be disabled. + * Determines if the tab should be disabled. Note that disabled tabs can + * still be accessed via the keyboard when navigating through the tablist. * * @default false */ - disabled?: boolean; + disabled?: Ariakit.TabProps[ 'disabled' ]; /** - * The type of component to render the tab button as. If this prop is not - * provided, the tab button will be rendered as a `button` element. + * Allows the component to be rendered as a different HTML element or React + * component. The value can be a React element or a function that takes in the + * original component props and gives back a React element with the props + * merged. + * + * By default, the tab will be rendered as a `button` element. */ - render?: React.ReactElement; + render?: Ariakit.TabProps[ 'render' ]; }; export type TabPanelProps = { /** - * The children elements, generally the content to display on the tabpanel. + * The contents of the tab panel. */ - children?: React.ReactNode; + children?: Ariakit.TabPanelProps[ 'children' ]; /** - * A unique identifier for the tabpanel, which is used to generate an - * instanced id for the underlying element. - * The value of this prop should match with the value of the `tabId` prop on - * the corresponding `Tabs.Tab` component. + * The unique `id` of the `Tabs.Tab` component controlling this panel. This + * connection is used to assign the `aria-labelledby` attribute to the tab + * panel and to determine if the tab panel should be visible. + * + * If not provided, this link is automatically established by matching the + * order of `Tabs.Tab` and `Tabs.TabPanel` elements in the DOM. */ - tabId: string; + tabId: NonNullable< Ariakit.TabPanelProps[ 'tabId' ] >; /** * Determines whether or not the tabpanel element should be focusable. * If `false`, pressing the tab key will skip over the tabpanel, and instead @@ -117,5 +157,5 @@ export type TabPanelProps = { * * @default true */ - focusable?: boolean; + focusable?: Ariakit.TabPanelProps[ 'focusable' ]; }; From 219696636c990e558a47ace60c15d8e3e05ca1af Mon Sep 17 00:00:00 2001 From: James Koster Date: Fri, 18 Oct 2024 15:09:06 +0100 Subject: [PATCH 25/30] Add foundations:elevation to storybook (#66124) Co-authored-by: jameskoster Co-authored-by: mirka <0mirka00@git.wordpress.org> Co-authored-by: ciampo Co-authored-by: tyxla --- .../foundations/design-language/elevation.mdx | 87 ++++++++ .../static/elevation-examples.svg | 170 ++++++++++++++ .../design-language/static/elevation.svg | 208 ++++++++++++++++++ .../design-language/static/wp-logo@2x.png | Bin 0 -> 5395 bytes storybook/stories/tokens/elevation.mdx | 2 +- 5 files changed, 466 insertions(+), 1 deletion(-) create mode 100644 storybook/stories/foundations/design-language/elevation.mdx create mode 100644 storybook/stories/foundations/design-language/static/elevation-examples.svg create mode 100644 storybook/stories/foundations/design-language/static/elevation.svg create mode 100644 storybook/stories/foundations/design-language/static/wp-logo@2x.png diff --git a/storybook/stories/foundations/design-language/elevation.mdx b/storybook/stories/foundations/design-language/elevation.mdx new file mode 100644 index 00000000000000..11591568b3600c --- /dev/null +++ b/storybook/stories/foundations/design-language/elevation.mdx @@ -0,0 +1,87 @@ +import { Meta, Typeset } from '@storybook/addon-docs/blocks'; +import elevation from './static/elevation.svg'; +import elevationExamples from './static/elevation-examples.svg'; + + + +# Elevation + +{/* Leaving alt text empty since this image is presentational */} + + + +Elevation, through the use of shadows, visually indicates the layers where overlapping UI elements reside. Shadows create the illusion of depth, showing how one UI element is positioned above another on the z-axis. Elevation should be used to organize elements, establish a clear hierarchy, and draw focus to key components. + +In the WordPress Design System there are four levels of elevation correlating to specific uses: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ElevationUsageExamples
Extra small + Applied to large sections and containers that group related content + and controls. These containers may shift or overlap other content + without causing too much visual disruption + Preview Frame, Content Frame
Small + Used for components that provide contextual feedback without being + overly intrusive. Small elevation is ideal for non-interruptive elements + that subtly surface additional information or actions. + Tooltips, Snackbars
Medium + Applied to components that offer additional actions to the user. This + elevation level helps differentiate actionable elements that appear in + context without requiring major focus shifts. + Menus, Command Palette
Large + Reserved for components that confirm actions or require decisions, large + elevation is used for more interruptive elements that demand user attention + to complete important tasks. + Modals
+ +## Accessibility considerations + +* Interactive Elements: Elevation can be an important visual cue to indicate which elements are interactive, +particularly for folks with monochromatic color blindness. Elevating buttons, handles, and other interactive +elements makes them stand out, helping users quickly identify where they can take action. +* Focus: Higher levels of elevation can be used to draw attention to components that require user interaction +or confirmation, such as modals, guides, and decision-based tasks, enhancing accessibility by reducing +cognitive load. + +## Examples + +The diagram below visually demonstrates the examples outlined in the table above. + +Diagram illustrating elevation levels + +1. Content frame and Preview frame +2. Snackbar +3. Menu +4. Modal + +## Tokens + +Use tokens to apply elevation in your work. Please refer to the [Tokens section](?path=/docs/tokens-elevation--page) to learn more. diff --git a/storybook/stories/foundations/design-language/static/elevation-examples.svg b/storybook/stories/foundations/design-language/static/elevation-examples.svg new file mode 100644 index 00000000000000..516313cbca4df3 --- /dev/null +++ b/storybook/stories/foundations/design-language/static/elevation-examples.svg @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/storybook/stories/foundations/design-language/static/elevation.svg b/storybook/stories/foundations/design-language/static/elevation.svg new file mode 100644 index 00000000000000..737e7955ad9df6 --- /dev/null +++ b/storybook/stories/foundations/design-language/static/elevation.svg @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/storybook/stories/foundations/design-language/static/wp-logo@2x.png b/storybook/stories/foundations/design-language/static/wp-logo@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a95cd961902b01cbd7635676f703ba9da135642d GIT binary patch literal 5395 zcmV+u73}JXP)`}Fe4~1Bq=T=CoUr>FC!=~Bq%N;CNLx@Fd!f>A}22+DJ~-@E+Z)~BPcB+DK8`` zFC-~2Bq=WE+i)}9Y%eH z0000qbW%=J03a}6aF3tAfKWgX&+q@QVDHaxuYbRg-`}86Aa9Rwa8MA>kk7DCP%w~> zug_r5UtfUFZyD`x000y6Nklr*?A86`e{Ezyqu{PA;iUMRx(Pc_Fvx_UKjs98}dG zn4xB6cN;F&#(ZQdAXEVoSjJv>*0Brt|okZ z_g;8utGgxJ(+lET_&dO}lZAdoAv=hPjhmhy0?1u&MSsigoLCzmxQqrl6mLibJp zE4B!xtKi<@26gXv(i+nOz)K?w6Lo)9M;rFc%YvZ|Kv&bpvI^1)&H%NgB|umC(Y3>) zX_ecu0A2N&wz5JVVHC=O!>)W59uHSyCan*YwSulxxI=nWa5t)Dp`c`eZFj{^Vl=8% zmZ4NZS48?5ZM;gPCTp#RV}gY5+pF+fB>&mk!84@@o@Jq=jW>xjVM}{YBH|P!2^;sI zDf1HKFML>dMdwKM5{| zV1gW2VlkcY8Wi;Q%whw|U?H-Tt8ig?&pGb<_KuPIb}*IhB5{TPn%oi4rUV&pHle za@Q~$o@dhxbF`{S+3302wjnll&iW6Qb z!J*IGdD=cCZ!*8Gm{;%^o3tO&38~c>In5Xyp+OWlYy3br2%VSd27cK ziN?Pr(yVR;4y&S6&qm*uS;>edwmc_25CHN&45x}Mxl7hfm zrO`;s-GTk*NF?}$nD`Cf>wSC;YPgmfR*gXd4Fk>!+q5-+<>YdXhesXrEpQE3zOdr! zZz-@wh4{j~+|*qY5Tm<(IUm^VYSy?(74pGi z4-5__zmSFf#tjaH%{t=z=4>`KTOu~ErQuC4|CHF3sf9erSIZKu#Hkj@0dEB6D4@Sf zOl9K!ONbT)3*Jb4Xl`z5^1xzAmJzVXg)Rh(e5KreRYJj+$Q$k?W%?6GRw?gAriD6w zCnsN}Dg)b0a)YLX6+{$q?(CB<_aT)2(=1hbEU*3YW5yObV(aqSe>y+oH%Z5>mVh}av}GA{#Duk{{i`n$YH{Fu`J8p(^=MV^${9wkHXeLm?7u~L;|fH=18r)jC-nu z5PmGw{+SZW7i_rob682}#&mjeDrPq(y{Ye~8X7pH8PL{-%fJene9Z7lcG!2_3h!-j z%wEGqcAqU)I#^$Zm+YjW^hvL(Qe|jiZjv8{elkbMmUQI}VA3M7TnW1$xZVNQ6*!EW z4pkzoBC;s9Zj{x9wftxm#JW7NDow6Rl!Kyd$o$V%j|C=_tJ}l=07njLvSp)y)kSO= z`VHf4m&iT1JT0sd35wtLQv`6Yd%8@a z%}(Btd#}CwZJo|h;QjL8HY=wFi0t`>jWm=#mV3_A!X5g7rec3eFO%$Dy49f)mVg~7 zA#f!vbIJNs4Vo*pf1vZo{O4Z}1k0&uL4_`mK!%^1^Bebr({y?Q9RRFOmz5$)!yxC5 zc*jHw^8%Y0)cbzQf{?@Q19f@I*V9NY8Y8DGN1tTFn18WMXY6`oxg|%h5tn@SXIE3=6pyf)YY$KBsmHB! zv`f-K5*6rjzJh~Ab9dy;t3eYUXUD)A)IA;EjGW!#tZu8OoA&6&0F%5qT3F?1;c_6# zQOGVbwNR8$$4-fC*+ocjZWT?9+_@A}wq)$44Qynz{_g+qsh{y{rY$+WCgpoFM} zsuFJGU1ugm2M!j^y*OBWU`D_y(1jcL4f(Ed(XhweZhDXHUtpE**!!z{2Jxoe!P4kjn(??+6=>WrMZM0qg#6g@x2f zC2QMv<|X0p}r(|{#69QfBAbC%_khPxB6BG41-{BJ6_7B2J*phK{T76v)f8p{#YKnV{( zJ{Tb|2dp|9EQt8a#D8qUy_HvTTfkKstiyw(u+qT_=}G$jLZCHoc<0;}!%~%_g#}yL zg7ZdFf8bzAFF8=cQ|x>~39a6X^ab@7B<}<)bW8-lQX895CRn?? zxeP6A6yU+UJRFZ$ObbN`Q43}5C|`PmhOv!XX3f2QTv%V?H8@ybP)87~*jtJfa<(cVXsweCCCC^6 z3$EMgO8EJV7B1k-1}pDe3q=W03sDIrqA{?r?UIAl;c68I772}Q_UeXwuxg1{O-*DN zJD8Kj(dss|VReim2fjvAyRm~iqlL>mwGxx7hK$jg6D3r!6D0&#Fxz0#Z3$TB!&OAC zo(ouYm_s%70PL%NJ zBNp1ijGi%AvyzWU|0zrFm!Cdzuw>~g0)tjAw1zTo4T^=`;t=?Nfo-d#SS>2-p92pQ zEfgixw6GN=1h9Tylw*O-v41pFJ2x0~QN(Xz~irW<}T!9jjIu=_}95k@X@LgM@ zcTpW{`D9?NLTE^r(z-Xg?!Yu2dc~&h};++7j zyz+{{`z>ZZ0arelpoJyD4ntpf+VxmP#8J`X*JIfy25Yq8WbqZG%DDrr zR#oD;J*~wr?`ld0BLS+MSCmjLhW%ne2{+OYRhCL&w}TcogqI?F+p{ECA~%Gr{1v1o z0ap81u%y?=wA|32ob9|f`nkO}_BaA##l=h7@aJ7GRS*g2B3CP#N3<}25>}96G5^tz zIf>o{UCap%e9iloR-ZG#vZ+T|Uk=u&K!u@Tfqk~0<=py>bP>UdbS>RNwggqQFs4F5 z=nd-)Y9Up^QN%dIw6LTpSV8_b)>&O-6I>d&+F$2xmUgY25qBwkI9N;AU)eqiEGO?@ zo2p!R1WRvN30Sh#o&ZO*5R?$&LKr9^gq|JxAYVREmC*_kvdD+2aOD6=Ml2oymc^dW zc*y+MK_co`3YJgR*s9ZWJ4UA$u=Iv&ptjMG*mH|4ukHBA z&y!I&-QEG0iVCTOtStlU3$8u5WM^chb2gX-CUah}-AO3J6@b{M62m_b?#sdU*d10- z6^#i4)eNeep)}UWHHZ>IXO(n7SMc$kJHL0s1bbVw1LP(Kx?r#-;d2I*Tdi=)ZLN%X zyM~apOXgtsT}7L$uWQLcG-WYQ!NUgCVXS_=)Q>SmN{xKQb6!RsVm&=D}F){goX@tz~r9dmXP(R#y`^sGaz-G+K5X zC%%%$p%!~SoiuVcSKeQ_C^=#etmdSlm4?FAFl6o7w2l`gq*}NUCG17VDF!UAg@$04 zQNM5Uzh5V=N4ep~R}8QC5prLd2mJvN^=T2Ww3Y?&i-%`x?50p$@XfU zL!YE2eEv%ZMo0dUqm$oCOA=eBks2l>d z?tup#;a!KvS#b)Mx$_x)3uQf4(Qmjk>L_ar10XDFloE z1yHH2eZ^?;AKp=T0TzUSzFb#q2{#+L!x_Fv@OrnmW82dS$PJst`jh^)4&dicbKi3G zZg0mVXaA~fQd1KpBwG0AFqX9jd`--T+Ai#TgeQ@n-ju!lbk&kK$go; zLJVxxfUCKLwYO&P=1wQ9WfnKULT$||1wYZj&l3CIq`jwELe_evj`+5}{BL`Aw5m7^ zgaLTctVv8vtyJU5;B(n_gK+oqZyh)G}QMnB{W6)hD48NlItr3PjiGWcqzRohKn~)rSD~Ke7d)PeLha^}Uq<{tL39CVI7s zB^Y^J$;TW3EUPujy{wCNx&B%bHImaBzySNb`|{HeZ^6yWgLA=3ps!C#ZkzNzMUR?yx-|E>8k-m}QA&VPN02HDYySZtgp5d3z zdLtG<>p#c{NA-yldp?Z%732nlg_KK6&q*_nuk57XjcksZ{5dj*%*O%V6hg x@6w*tCIWb0EWs@cU&aUK>+EjD5CFhW`v)S+^3-dh8~p$P002ovPDHLkV1hxt=*R#7 literal 0 HcmV?d00001 diff --git a/storybook/stories/tokens/elevation.mdx b/storybook/stories/tokens/elevation.mdx index d00de6dbde7fab..5bd7dbf79a2622 100644 --- a/storybook/stories/tokens/elevation.mdx +++ b/storybook/stories/tokens/elevation.mdx @@ -5,7 +5,7 @@ import { ElevationTable } from './components.tsx'; # Elevation tokens -This document outlines the various tokens relating to elevation in the WordPress design system. +This document outlines the various tokens relating to [elevation](?path=/docs/foundations-design-language-elevation--page) in the WordPress design system. ## Values From ec9625e7fa24d0112428906917e6615651f5ba89 Mon Sep 17 00:00:00 2001 From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:00:12 +0200 Subject: [PATCH 26/30] Block bindings: allow only string field types in bindings. (#66174) * Filter fields by type in post meta * Add e2e test * Allow only strings * Update test to expect numbers and integers to be hidden * Update tests to check for label instead of key * Adapt object custom field * Adapt e2e tests * Add `type` prop to `getFieldsList` * Adapt testing source * Reduce diff * Add `attribute` to dependencies --------- Co-authored-by: SantosGuillamot Co-authored-by: cbravobernal Co-authored-by: artemiomorales Co-authored-by: gziolo --- .../block-editor/src/hooks/block-bindings.js | 63 +++++++++----- packages/e2e-tests/plugins/block-bindings.php | 86 +++++++++++++++++++ packages/editor/src/bindings/post-meta.js | 1 + .../various/block-bindings/post-meta.spec.js | 49 +++++++++++ 4 files changed, 176 insertions(+), 23 deletions(-) diff --git a/packages/block-editor/src/hooks/block-bindings.js b/packages/block-editor/src/hooks/block-bindings.js index 5c002613831ce1..694c9c1c0bb3cc 100644 --- a/packages/block-editor/src/hooks/block-bindings.js +++ b/packages/block-editor/src/hooks/block-bindings.js @@ -5,6 +5,7 @@ import { __ } from '@wordpress/i18n'; import { getBlockBindingsSource, getBlockBindingsSources, + getBlockType, } from '@wordpress/blocks'; import { __experimentalItemGroup as ItemGroup, @@ -29,6 +30,7 @@ import { import { unlock } from '../lock-unlock'; import InspectorControls from '../components/inspector-controls'; import BlockContext from '../components/block-context'; +import { useBlockEditContext } from '../components/block-edit'; import { useBlockBindingsUtils } from '../utils/block-bindings'; import { store as blockEditorStore } from '../store'; @@ -50,9 +52,20 @@ const useToolsPanelDropdownMenuProps = () => { }; function BlockBindingsPanelDropdown( { fieldsList, attribute, binding } ) { + const { clientId } = useBlockEditContext(); const registeredSources = getBlockBindingsSources(); const { updateBlockBindings } = useBlockBindingsUtils(); const currentKey = binding?.args?.key; + const attributeType = useSelect( + ( select ) => { + const { name: blockName } = + select( blockEditorStore ).getBlock( clientId ); + const _attributeType = + getBlockType( blockName ).attributes?.[ attribute ]?.type; + return _attributeType === 'rich-text' ? 'string' : _attributeType; + }, + [ clientId, attribute ] + ); return ( <> { Object.entries( fieldsList ).map( ( [ name, fields ], i ) => ( @@ -63,29 +76,33 @@ function BlockBindingsPanelDropdown( { fieldsList, attribute, binding } ) { { registeredSources[ name ].label } ) } - { Object.entries( fields ).map( ( [ key, args ] ) => ( - - updateBlockBindings( { - [ attribute ]: { - source: name, - args: { key }, - }, - } ) - } - name={ attribute + '-binding' } - value={ key } - checked={ key === currentKey } - > - - { args?.label } - - - { args?.value } - - - ) ) } + { Object.entries( fields ) + .filter( + ( [ , args ] ) => args?.type === attributeType + ) + .map( ( [ key, args ] ) => ( + + updateBlockBindings( { + [ attribute ]: { + source: name, + args: { key }, + }, + } ) + } + name={ attribute + '-binding' } + value={ key } + checked={ key === currentKey } + > + + { args?.label } + + + { args?.value } + + + ) ) } { i !== Object.keys( fieldsList ).length - 1 && ( diff --git a/packages/e2e-tests/plugins/block-bindings.php b/packages/e2e-tests/plugins/block-bindings.php index ffbe50ab3cc902..b86673c2c523d0 100644 --- a/packages/e2e-tests/plugins/block-bindings.php +++ b/packages/e2e-tests/plugins/block-bindings.php @@ -21,14 +21,17 @@ function gutenberg_test_block_bindings_registration() { 'text_field' => array( 'label' => 'Text Field Label', 'value' => 'Text Field Value', + 'type' => 'string', ), 'url_field' => array( 'label' => 'URL Field Label', 'value' => $testing_url, + 'type' => 'string', ), 'empty_field' => array( 'label' => 'Empty Field Label', 'value' => '', + 'type' => 'string', ), ); @@ -106,6 +109,89 @@ function gutenberg_test_block_bindings_registration() { 'type' => 'string', ) ); + // Register different types of custom fields for testing. + register_meta( + 'post', + 'string_custom_field', + array( + 'label' => 'String custom field', + 'default' => '', + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + ) + ); + register_meta( + 'post', + 'object_custom_field', + array( + 'label' => 'Object custom field', + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'object', + 'properties' => array( + 'foo' => array( + 'type' => 'string', + ), + ), + ), + ), + 'single' => true, + 'type' => 'object', + ) + ); + register_meta( + 'post', + 'array_custom_field', + array( + 'label' => 'Array custom field', + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + ), + 'single' => true, + 'type' => 'array', + 'default' => array(), + ) + ); + register_meta( + 'post', + 'number', + array( + 'label' => 'Number custom field', + 'type' => 'number', + 'show_in_rest' => true, + 'single' => true, + 'default' => 5.5, + ) + ); + register_meta( + 'post', + 'integer', + array( + 'label' => 'Integer custom field', + 'type' => 'integer', + 'show_in_rest' => true, + 'single' => true, + 'default' => 5, + ) + ); + register_meta( + 'post', + 'boolean', + array( + 'label' => 'Boolean custom field', + 'type' => 'boolean', + 'show_in_rest' => true, + 'single' => true, + 'default' => true, + ) + ); + // Register CPT custom fields. register_meta( 'post', diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index 9198ac0fe41e1e..a3602ce7d62076 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -60,6 +60,7 @@ function getPostMetaFields( select, context ) { entityMetaValues?.[ key ] ?? // When using the default, an empty string IS NOT a valid value. ( props.default || undefined ), + type: props.type, }; } } ); diff --git a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js index d82def6feb66bf..32334bfc777f2a 100644 --- a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js +++ b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js @@ -547,5 +547,54 @@ test.describe( 'Post Meta source', () => { .filter( { hasText: 'Movie field label' } ); await expect( movieField ).toBeVisible(); } ); + test( 'should not be possible to connect non-supported fields through the attributes panel', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + } ); + await page.getByLabel( 'Attributes options' ).click(); + await page + .getByRole( 'menuitemcheckbox', { + name: 'Show content', + } ) + .click(); + await page + .getByRole( 'button', { + name: 'content', + } ) + .click(); + await expect( + page.getByRole( 'menuitemradio', { + name: 'String custom field', + } ) + ).toBeVisible(); + await expect( + page.getByRole( 'menuitemradio', { + name: 'Number custom field', + } ) + ).toBeHidden(); + await expect( + page.getByRole( 'menuitemradio', { + name: 'Integer custom field', + } ) + ).toBeHidden(); + await expect( + page.getByRole( 'menuitemradio', { + name: 'Boolean custom field', + } ) + ).toBeHidden(); + await expect( + page.getByRole( 'menuitemradio', { + name: 'Object custom field', + } ) + ).toBeHidden(); + await expect( + page.getByRole( 'menuitemradio', { + name: 'Array custom field', + } ) + ).toBeHidden(); + } ); } ); } ); From 1bfac715be20febb532007d0ac8a530682a9e8aa Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 18 Oct 2024 16:21:57 +0100 Subject: [PATCH 27/30] Zoom Out: Hide slots and grouping buttons (#66243) Co-authored-by: youknowriad Co-authored-by: getdave Co-authored-by: ajlende Co-authored-by: draganescu Co-authored-by: ntsekouras Co-authored-by: talldan Co-authored-by: richtabor --- .../src/components/block-toolbar/index.js | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index 58a7b2b09bb2cd..77b10149aaf226 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -69,6 +69,9 @@ export function PrivateBlockToolbar( { hasParentPattern, hasContentOnlyLocking, showShuffleButton, + showSlots, + showGroupButtons, + showLockButtons, } = useSelect( ( select ) => { const { getBlockName, @@ -135,6 +138,9 @@ export function PrivateBlockToolbar( { hasParentPattern: _hasParentPattern, hasContentOnlyLocking: _hasTemplateLock, showShuffleButton: isZoomOut(), + showSlots: ! isZoomOut(), + showGroupButtons: ! isZoomOut(), + showLockButtons: ! isZoomOut(), }; }, [] ); @@ -195,11 +201,13 @@ export function PrivateBlockToolbar( { > - { ! isMultiToolbar && isDefaultEditingMode && ( - - ) } + { ! isMultiToolbar && + isDefaultEditingMode && + showLockButtons && ( + + ) } } + isMultiToolbar && + showGroupButtons && } { showShuffleButton && ( ) } - { shouldShowVisualToolbar && ( + { shouldShowVisualToolbar && showSlots && ( <> Date: Fri, 18 Oct 2024 16:43:20 +0100 Subject: [PATCH 28/30] iAPI: Add tests for handling arrays in `deepMerge()` (#66218) * add new tests * updated tests * remove one extra test Co-authored-by: michalczaplinski Co-authored-by: DAreRodz --- .../src/proxies/test/deep-merge.ts | 83 +++++++++++++++---- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/packages/interactivity/src/proxies/test/deep-merge.ts b/packages/interactivity/src/proxies/test/deep-merge.ts index 267e4850f9af91..aaa762cb979f3c 100644 --- a/packages/interactivity/src/proxies/test/deep-merge.ts +++ b/packages/interactivity/src/proxies/test/deep-merge.ts @@ -251,24 +251,6 @@ describe( 'Interactivity API', () => { expect( result ).toEqual( { a: 1 } ); } ); - it( 'should handle arrays', () => { - const target = { a: [ 1, 2 ] }; - const source = { a: [ 3, 4 ] }; - const result = {}; - deepMerge( result, target ); - deepMerge( result, source ); - expect( result ).toEqual( { a: [ 3, 4 ] } ); - } ); - - it( 'should handle arrays when overwrite is false', () => { - const target = { a: [ 1, 2 ] }; - const source = { a: [ 3, 4 ] }; - const result = {}; - deepMerge( result, target, false ); - deepMerge( result, source, false ); - expect( result ).toEqual( { a: [ 1, 2 ] } ); - } ); - it( 'should handle null values', () => { const target = { a: 1, b: null }; const source = { b: 2, c: null }; @@ -500,5 +482,70 @@ describe( 'Interactivity API', () => { expect( spy ).toHaveBeenCalledTimes( 3 ); expect( deepValue ).toBe( 'value 2' ); } ); + + describe( 'arrays', () => { + it( 'should handle arrays', () => { + const target = { a: [ 1, 2 ] }; + const source = { a: [ 3, 4 ] }; + const result = {}; + deepMerge( result, target ); + deepMerge( result, source ); + expect( result ).toEqual( { a: [ 3, 4 ] } ); + } ); + + it( 'should handle arrays when overwrite is false', () => { + const target = { a: [ 1, 2 ] }; + const source = { a: [ 3, 4 ] }; + const result = {}; + deepMerge( result, target, false ); + deepMerge( result, source, false ); + expect( result ).toEqual( { a: [ 1, 2 ] } ); + } ); + + it( 'should add new array from source if not present in target', () => { + const target = { a: 1 }; + const source = { arr: [ 1, 2, 3 ] }; + const result = {}; + deepMerge( result, target ); + deepMerge( result, source ); + expect( result ).toEqual( { a: 1, arr: [ 1, 2, 3 ] } ); + } ); + + it( 'should handle nested arrays', () => { + const target = { nested: { arr: [ 1, 2 ] } }; + const source = { nested: { arr: [ 3, 4, 5 ] } }; + const result = {}; + deepMerge( result, target ); + deepMerge( result, source ); + expect( result ).toEqual( { nested: { arr: [ 3, 4, 5 ] } } ); + } ); + + it( 'should handle object with array as target and object with object as source', () => { + const target = { arr: [ 1, 2, 3 ] }; + const source = { arr: { 1: 'two', 3: 'four' } }; + const result: any = {}; + deepMerge( result, target ); + deepMerge( result, source ); + expect( result ).toEqual( { arr: { 1: 'two', 3: 'four' } } ); + } ); + + it( 'should handle object with object as target and object with array as source', () => { + const target = { arr: { 0: 'zero', 2: 'two' } }; + const source = { arr: [ 'a', 'b', 'c' ] }; + const result = {}; + deepMerge( result, target ); + deepMerge( result, source ); + expect( result ).toEqual( { arr: [ 'a', 'b', 'c' ] } ); + } ); + + it( 'should handle objects with arrays containing object elements', () => { + const target = { arr: [ { a: 1 }, { b: 2 } ] }; + const source = { arr: [ { a: 2 }, { c: 3 } ] }; + const result: any = {}; + deepMerge( result, target ); + deepMerge( result, source ); + expect( result ).toEqual( { arr: [ { a: 2 }, { c: 3 } ] } ); + } ); + } ); } ); } ); From 0ae2cb8e520292f2726fa6c63676b91333819168 Mon Sep 17 00:00:00 2001 From: Michal Date: Fri, 18 Oct 2024 16:53:01 +0100 Subject: [PATCH 29/30] iAPI: Add comments to the `deepMerge()` function (#66220) * update comment * Add comments Co-authored-by: michalczaplinski Co-authored-by: DAreRodz --- packages/interactivity/src/proxies/state.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/interactivity/src/proxies/state.ts b/packages/interactivity/src/proxies/state.ts index d4d573acba8847..f9af257bada2ea 100644 --- a/packages/interactivity/src/proxies/state.ts +++ b/packages/interactivity/src/proxies/state.ts @@ -300,6 +300,8 @@ const deepMergeRecursive = ( source: any, override: boolean = true ) => { + // If target is not a plain object and the source is, we don't need to merge + // them because the source will be used as the new value of the target. if ( ! ( isPlainObject( target ) && isPlainObject( source ) ) ) { return; } @@ -317,38 +319,50 @@ const deepMergeRecursive = ( hasPropSignal( proxy, key ) && getPropSignal( proxy, key ); + // Handle getters and setters if ( typeof desc.get === 'function' || typeof desc.set === 'function' ) { if ( override || isNew ) { + // Because we are setting a getter or setter, we need to use + // Object.defineProperty to define the property on the target object. Object.defineProperty( target, key, { ...desc, configurable: true, enumerable: true, } ); + // Update the getter in the property signal if it exists if ( desc.get && propSignal ) { propSignal.setGetter( desc.get ); } } + + // Handle nested objects } else if ( isPlainObject( source[ key ] ) ) { if ( isNew || ( override && ! isPlainObject( target[ key ] ) ) ) { + // Create a new object if the property is new or needs to be overridden target[ key ] = {}; if ( propSignal ) { + // Create a new proxified state for the nested object const ns = getNamespaceFromProxy( proxy ); propSignal.setValue( proxifyState( ns, target[ key ] as Object ) ); } } + // Both target and source are plain objects, merge them recursively if ( isPlainObject( target[ key ] ) ) { deepMergeRecursive( target[ key ], source[ key ], override ); } + + // Handle primitive values and non-plain objects } else if ( override || isNew ) { Object.defineProperty( target, key, desc ); if ( propSignal ) { const { value } = desc; const ns = getNamespaceFromProxy( proxy ); + // Proxify the value if necessary before setting it in the signal propSignal.setValue( shouldProxy( value ) ? proxifyState( ns, value ) : value ); From bcddcb6ad6d2cf7b39fc528037ccabd07b730212 Mon Sep 17 00:00:00 2001 From: Troy Chaplin <15524465+troychaplin@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:09:53 -0400 Subject: [PATCH 30/30] docs: example for getSelectedBlock (#66108) * add: example for getSelectedBlock * change: build docs --- .../data/data-core-block-editor.md | 33 +++++++++++++++++++ packages/block-editor/src/store/selectors.js | 33 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index 956e8dd010581a..437f7be20f7705 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -738,6 +738,39 @@ _Returns_ Returns the currently selected block, or null if there is no selected block. +_Usage_ + +```js +import { select } from '@wordpress/data'; +import { store as blockEditorStore } from '@wordpress/block-editor'; + +// Set initial active block client ID +let activeBlockClientId = null; + +const getActiveBlockData = () => { + const activeBlock = select( blockEditorStore ).getSelectedBlock(); + + if ( activeBlock && activeBlock.clientId !== activeBlockClientId ) { + activeBlockClientId = activeBlock.clientId; + + // Get active block name and attributes + const activeBlockName = activeBlock.name; + const activeBlockAttributes = activeBlock.attributes; + + // Log active block name and attributes + console.log( activeBlockName, activeBlockAttributes ); + } +}; + +// Subscribe to changes in the editor +// wp.data.subscribe(() => { +// getActiveBlockData() +// }) + +// Update active block data on click +// onclick="getActiveBlockData()" +``` + _Parameters_ - _state_ `Object`: Global application state. diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 210cd26aeaa954..3fdaadb24fc129 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -542,6 +542,39 @@ export function getSelectedBlockClientId( state ) { * * @param {Object} state Global application state. * + * @example + * + *```js + * import { select } from '@wordpress/data' + * import { store as blockEditorStore } from '@wordpress/block-editor' + * + * // Set initial active block client ID + * let activeBlockClientId = null + * + * const getActiveBlockData = () => { + * const activeBlock = select(blockEditorStore).getSelectedBlock() + * + * if (activeBlock && activeBlock.clientId !== activeBlockClientId) { + * activeBlockClientId = activeBlock.clientId + * + * // Get active block name and attributes + * const activeBlockName = activeBlock.name + * const activeBlockAttributes = activeBlock.attributes + * + * // Log active block name and attributes + * console.log(activeBlockName, activeBlockAttributes) + * } + * } + * + * // Subscribe to changes in the editor + * // wp.data.subscribe(() => { + * // getActiveBlockData() + * // }) + * + * // Update active block data on click + * // onclick="getActiveBlockData()" + *``` + * * @return {?Object} Selected block. */ export function getSelectedBlock( state ) {