From c34280bbf0593f4bf0bc24b210bfe42afb61e119 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 18 May 2023 09:26:47 +1000 Subject: [PATCH 01/26] Styles Navigation Screen: Add Style Book (#50566) * Styles Navigation Screen: Add button to open Style Book * Remove state and useEffect, use async / await instead * Try exposing Style Book in browse mode * Try hiding Style Book tabs, add keyboard behaviour * Revert background color removal * Clear the editor canvas container view when accessing the main navigation screen * Fix enableResizing, move prevent default to the Style Book component for simplicity * Tidy code a little Co-authored-by: ramon --------- Co-authored-by: ramon --- .../index.js | 90 +++++++-- .../sidebar-navigation-screen-main/index.js | 21 ++- .../src/components/style-book/index.js | 174 +++++++++++++----- .../src/components/style-book/style.scss | 19 ++ 4 files changed, 239 insertions(+), 65 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js index 06e486739ed74..a7b8add9dd54f 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-global-styles/index.js @@ -2,10 +2,11 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { edit } from '@wordpress/icons'; +import { edit, seen } from '@wordpress/icons'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { __experimentalNavigatorButton as NavigatorButton } from '@wordpress/components'; +import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -16,6 +17,7 @@ import { unlock } from '../../private-apis'; import { store as editSiteStore } from '../../store'; import SidebarButton from '../sidebar-button'; import SidebarNavigationItem from '../sidebar-navigation-item'; +import StyleBook from '../style-book'; export function SidebarNavigationItemGlobalStyles( props ) { const { openGeneralSidebar } = useDispatch( editSiteStore ); @@ -51,26 +53,74 @@ export function SidebarNavigationItemGlobalStyles( props ) { export default function SidebarNavigationScreenGlobalStyles() { const { openGeneralSidebar } = useDispatch( editSiteStore ); - const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); + const isMobileViewport = useViewportMatch( 'medium', '<' ); + const { setCanvasMode, setEditorCanvasContainerView } = unlock( + useDispatch( editSiteStore ) + ); + + const isStyleBookOpened = useSelect( + ( select ) => + 'style-book' === + unlock( select( editSiteStore ) ).getEditorCanvasContainerView(), + [] + ); + + const openGlobalStyles = async () => + Promise.all( [ + setCanvasMode( 'edit' ), + openGeneralSidebar( 'edit-site/global-styles' ), + ] ); + + const openStyleBook = async () => { + await openGlobalStyles(); + // Open the Style Book once the canvas mode is set to edit, + // and the global styles sidebar is open. This ensures that + // the Style Book is not prematurely closed. + setEditorCanvasContainerView( 'style-book' ); + }; + return ( - } - actions={ - { - // switch to edit mode. - setCanvasMode( 'edit' ); - // open global styles sidebar. - openGeneralSidebar( 'edit-site/global-styles' ); - } } + <> + } + actions={ +
+ { ! isMobileViewport && ( + + setEditorCanvasContainerView( + ! isStyleBookOpened + ? 'style-book' + : undefined + ) + } + isPressed={ isStyleBookOpened } + /> + ) } + await openGlobalStyles() } + /> +
+ } + /> + { isStyleBookOpened && ! isMobileViewport && ( + false } + onClick={ openStyleBook } + onSelect={ openStyleBook } + showCloseButton={ false } + showTabs={ false } /> - } - /> + ) } + ); } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js index 7c9e4b3bf9196..7ad0dc07ae0f0 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-main/index.js @@ -7,8 +7,9 @@ import { } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { layout, symbol, navigation, styles, page } from '@wordpress/icons'; -import { useSelect } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; +import { useEffect } from '@wordpress/element'; /** * Internal dependencies @@ -16,6 +17,8 @@ import { store as coreStore } from '@wordpress/core-data'; import SidebarNavigationScreen from '../sidebar-navigation-screen'; import SidebarNavigationItem from '../sidebar-navigation-item'; import { SidebarNavigationItemGlobalStyles } from '../sidebar-navigation-screen-global-styles'; +import { unlock } from '../../private-apis'; +import { store as editSiteStore } from '../../store'; export default function SidebarNavigationScreenMain() { const hasNavigationMenus = useSelect( ( select ) => { @@ -36,6 +39,22 @@ export default function SidebarNavigationScreenMain() { const showNavigationScreen = process.env.IS_GUTENBERG_PLUGIN ? hasNavigationMenus : false; + + const editorCanvasContainerView = useSelect( ( select ) => { + return unlock( select( editSiteStore ) ).getEditorCanvasContainerView(); + }, [] ); + + const { setEditorCanvasContainerView } = unlock( + useDispatch( editSiteStore ) + ); + + // Clear the editor canvas container view when accessing the main navigation screen. + useEffect( () => { + if ( editorCanvasContainerView ) { + setEditorCanvasContainerView( undefined ); + } + }, [ editorCanvasContainerView, setEditorCanvasContainerView ] ); + return (
600, + 'is-button': !! onClick, } ) } style={ { color: textColor, @@ -206,53 +218,125 @@ function StyleBook( { isSelected, onSelect } ) { } } > { resizeObserver } - - { ( tab ) => ( - - ) } - + ) } + + ) : ( + + ) }
); } +const StyleBookBody = ( { + category, + examples, + isSelected, + onClick, + onSelect, + settings, + sizes, + title, +} ) => { + const [ isFocused, setIsFocused ] = useState( false ); + + // The presence of an `onClick` prop indicates that the Style Book is being used as a button. + // In this case, add additional props to the iframe to make it behave like a button. + const buttonModeProps = { + role: 'button', + onFocus: () => setIsFocused( true ), + onBlur: () => setIsFocused( false ), + onKeyDown: ( event ) => { + if ( event.defaultPrevented ) { + return; + } + const { keyCode } = event; + if ( onClick && ( keyCode === ENTER || keyCode === SPACE ) ) { + event.preventDefault(); + onClick( event ); + } + }, + onClick: ( event ) => { + if ( event.defaultPrevented ) { + return; + } + if ( onClick ) { + event.preventDefault(); + onClick( event ); + } + }, + readonly: true, + }; + + const buttonModeStyles = onClick + ? 'body { cursor: pointer; } body * { pointer-events: none; }' + : ''; + + return ( + + ); +}; + const Examples = memo( ( { className, examples, category, label, isSelected, onSelect } ) => { const composite = useCompositeState( { orientation: 'vertical' } ); @@ -263,7 +347,9 @@ const Examples = memo( aria-label={ label } > { examples - .filter( ( example ) => example.category === category ) + .filter( ( example ) => + category ? example.category === category : true + ) .map( ( example ) => ( { - onSelect( example.name ); + onSelect?.( example.name ); } } /> ) ) } diff --git a/packages/edit-site/src/components/style-book/style.scss b/packages/edit-site/src/components/style-book/style.scss index 6dcc1fec328ab..0ddefb055a8d8 100644 --- a/packages/edit-site/src/components/style-book/style.scss +++ b/packages/edit-site/src/components/style-book/style.scss @@ -1,3 +1,22 @@ +.edit-site-style-book { + // Ensure the style book fills the available vertical space. + // This is useful when the style book is used to fill a frame. + height: 100%; + &.is-button { + border-radius: $radius-block-ui * 4; + } +} + +.edit-site-style-book__iframe { + &.is-button { + border-radius: $radius-block-ui * 4; + } + &.is-focused { + outline: calc(2 * var(--wp-admin-border-width-focus)) solid var(--wp-admin-theme-color); + outline-offset: calc(-2 * var(--wp-admin-border-width-focus)); + } +} + .edit-site-style-book__tab-panel { .components-tab-panel__tabs { background: $white; From 671d72704b842228f5dc1a9e42dd433311be9ac4 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Thu, 18 May 2023 11:24:30 +1000 Subject: [PATCH 02/26] DateTime: Remove deprecated props (and fix static analysis action in trunk) (#50724) * DateTime: Try removing deprecated props * Update changelog * Remove additional references to the deprecated props * Bump caniuse-lite version --- package-lock.json | 6 +- .../publish-date-time-picker/index.js | 2 - packages/components/CHANGELOG.md | 4 + packages/components/src/date-time/README.md | 16 -- .../src/date-time/date-time/index.tsx | 172 ++---------------- .../src/date-time/date-time/styles.ts | 4 - .../src/date-time/stories/date-time.tsx | 4 - packages/components/src/date-time/types.ts | 16 -- .../test/__snapshots__/build.js.snap | 32 ++-- .../test/__snapshots__/build.js.snap | 4 +- 10 files changed, 42 insertions(+), 218 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6849e395790b6..040fe3911f377 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28177,9 +28177,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001434", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz", - "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==" + "version": "1.0.30001488", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz", + "integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==" }, "capital-case": { "version": "1.0.4", diff --git a/packages/block-editor/src/components/publish-date-time-picker/index.js b/packages/block-editor/src/components/publish-date-time-picker/index.js index 364fb09594802..418006cf854c1 100644 --- a/packages/block-editor/src/components/publish-date-time-picker/index.js +++ b/packages/block-editor/src/components/publish-date-time-picker/index.js @@ -29,8 +29,6 @@ function PublishDateTimePicker( /> diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index d4cc445dd379d..6c00d6e22027b 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- `DateTime`: Remove previously deprecated props, `__nextRemoveHelpButton` and `__nextRemoveResetButton` ([#50724](https://github.com/WordPress/gutenberg/pull/50724)). + ### Internal - `Modal`: Remove children container's unused class name ([#50655](https://github.com/WordPress/gutenberg/pull/50655)). diff --git a/packages/components/src/date-time/README.md b/packages/components/src/date-time/README.md index e01d9074953f2..c65cf6d3a1aae 100644 --- a/packages/components/src/date-time/README.md +++ b/packages/components/src/date-time/README.md @@ -26,8 +26,6 @@ const MyDateTimePicker = () => { currentDate={ date } onChange={ ( newDate ) => setDate( newDate ) } is12Hour={ true } - __nextRemoveHelpButton - __nextRemoveResetButton /> ); }; @@ -83,17 +81,3 @@ The day that the week should start on. 0 for Sunday, 1 for Monday, etc. - Required: No - Default: 0 - -### `__nextRemoveHelpButton`: `boolean` - -Start opting in to not displaying a Help button which will become the default in a future version. - -- Required: No -- Default: `false` - -### `__nextRemoveResetButton`: `boolean` - -Start opting in to not displaying a Reset button which will become the default in a future version. - -- Required: No -- Default: `false` diff --git a/packages/components/src/date-time/date-time/index.tsx b/packages/components/src/date-time/date-time/index.tsx index 123f20e76c640..a75b9fe57c14e 100644 --- a/packages/components/src/date-time/date-time/index.tsx +++ b/packages/components/src/date-time/date-time/index.tsx @@ -6,21 +6,16 @@ import type { ForwardedRef } from 'react'; /** * WordPress dependencies */ -import { useState, forwardRef } from '@wordpress/element'; +import { forwardRef } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; -import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ -import Button from '../../button'; import { default as DatePicker } from '../date'; import { default as TimePicker } from '../time'; import type { DateTimePickerProps } from '../types'; -import { Wrapper, CalendarHelp } from './styles'; -import { HStack } from '../../h-stack'; -import { Heading } from '../../heading'; -import { Spacer } from '../../spacer'; +import { Wrapper } from './styles'; export { DatePicker, TimePicker }; @@ -35,157 +30,26 @@ function UnforwardedDateTimePicker( onChange, events, startOfWeek, - __nextRemoveHelpButton = false, - __nextRemoveResetButton = false, }: DateTimePickerProps, ref: ForwardedRef< any > ) { - if ( ! __nextRemoveHelpButton ) { - deprecated( 'Help button in wp.components.DateTimePicker', { - since: '13.4', - version: '15.8', // One year of plugin releases. - hint: 'Set the `__nextRemoveHelpButton` prop to `true` to remove this warning and opt in to the new behaviour, which will become the default in a future version.', - } ); - } - if ( ! __nextRemoveResetButton ) { - deprecated( 'Reset button in wp.components.DateTimePicker', { - since: '13.4', - version: '15.8', // One year of plugin releases. - hint: 'Set the `__nextRemoveResetButton` prop to `true` to remove this warning and opt in to the new behaviour, which will become the default in a future version.', - } ); - } - - const [ calendarHelpIsVisible, setCalendarHelpIsVisible ] = - useState( false ); - - function onClickDescriptionToggle() { - setCalendarHelpIsVisible( ! calendarHelpIsVisible ); - } - return ( - { ! calendarHelpIsVisible && ( - <> - - - - ) } - { calendarHelpIsVisible && ( - - { __( 'Click to Select' ) } -
    -
  • - { __( - 'Click the right or left arrows to select other months in the past or the future.' - ) } -
  • -
  • { __( 'Click the desired day to select it.' ) }
  • -
- - { __( 'Navigating with a keyboard' ) } - -
    -
  • - - ↵ - - { - ' ' /* JSX removes whitespace, but a space is required for screen readers. */ - } - { __( 'Select the date in focus.' ) } -
  • -
  • - - ←/→ - - { - ' ' /* JSX removes whitespace, but a space is required for screen readers. */ - } - { __( - 'Move backward (left) or forward (right) by one day.' - ) } -
  • -
  • - - ↑/↓ - - { - ' ' /* JSX removes whitespace, but a space is required for screen readers. */ - } - { __( - 'Move backward (up) or forward (down) by one week.' - ) } -
  • -
  • - - { __( 'PgUp/PgDn' ) } - - { - ' ' /* JSX removes whitespace, but a space is required for screen readers. */ - } - { __( - 'Move backward (PgUp) or forward (PgDn) by one month.' - ) } -
  • -
  • - - { /* Translators: Home/End reffer to the 'Home' and 'End' buttons on the keyboard.*/ } - { __( 'Home/End' ) } - - { - ' ' /* JSX removes whitespace, but a space is required for screen readers. */ - } - { __( - 'Go to the first (Home) or last (End) day of a week.' - ) } -
  • -
-
- ) } - { ( ! __nextRemoveResetButton || ! __nextRemoveHelpButton ) && ( - - { ! __nextRemoveResetButton && - ! calendarHelpIsVisible && - currentDate && ( - - ) } - - { ! __nextRemoveHelpButton && ( - - ) } - - ) } + <> + + +
); } @@ -207,8 +71,6 @@ function UnforwardedDateTimePicker( * currentDate={ date } * onChange={ ( newDate ) => setDate( newDate ) } * is12Hour - * __nextRemoveHelpButton - * __nextRemoveResetButton * /> * ); * }; diff --git a/packages/components/src/date-time/date-time/styles.ts b/packages/components/src/date-time/date-time/styles.ts index a7f23eb276e04..51dc6e71f0c46 100644 --- a/packages/components/src/date-time/date-time/styles.ts +++ b/packages/components/src/date-time/date-time/styles.ts @@ -11,7 +11,3 @@ import { VStack } from '../../v-stack'; export const Wrapper = styled( VStack )` box-sizing: border-box; `; - -export const CalendarHelp = styled.div` - min-width: 260px; -`; diff --git a/packages/components/src/date-time/stories/date-time.tsx b/packages/components/src/date-time/stories/date-time.tsx index 53d369728d34d..447b9cf265ab8 100644 --- a/packages/components/src/date-time/stories/date-time.tsx +++ b/packages/components/src/date-time/stories/date-time.tsx @@ -52,10 +52,6 @@ const Template: ComponentStory< typeof DateTimePicker > = ( { export const Default: ComponentStory< typeof DateTimePicker > = Template.bind( {} ); -Default.args = { - __nextRemoveHelpButton: true, - __nextRemoveResetButton: true, -}; export const WithEvents: ComponentStory< typeof DateTimePicker > = Template.bind( {} ); diff --git a/packages/components/src/date-time/types.ts b/packages/components/src/date-time/types.ts index ae99a7082dcd2..02df096a866b9 100644 --- a/packages/components/src/date-time/types.ts +++ b/packages/components/src/date-time/types.ts @@ -73,20 +73,4 @@ export type DateTimePickerProps = Omit< DatePickerProps, 'onChange' > & * passed the date and time as an argument. */ onChange?: ( date: string | null ) => void; - - /** - * Start opting in to not displaying a Help button which will become the - * default in a future version. - * - * @default false - */ - __nextRemoveHelpButton?: boolean; - - /** - * Start opting in to not displaying a Reset button which will become - * the default in a future version. - * - * @default false - */ - __nextRemoveResetButton?: boolean; }; diff --git a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap index f5bf010b1ef8d..42102ca2cb132 100644 --- a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap +++ b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DependencyExtractionWebpackPlugin Webpack \`combine-assets\` should produce expected output: Asset file 'assets.php' should match snapshot 1`] = ` -" array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'bf200ecb3dcb6881a1f3'), 'fileB.js' => array('dependencies' => array('wp-token-list'), 'version' => '0af6c51a8e6ac934b85a')); +" array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'cf268e19006bef774112'), 'fileB.js' => array('dependencies' => array('wp-token-list'), 'version' => '7f3970305cf0aecb54ab')); " `; @@ -32,7 +32,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`combine-assets\` should pro `; exports[`DependencyExtractionWebpackPlugin Webpack \`dynamic-import\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('wp-blob'), 'version' => '782d84ec5d7303bb6bd2'); +" array('wp-blob'), 'version' => 'c8be4fceac30d1d00ca7'); " `; @@ -50,7 +50,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`dynamic-import\` should pro `; exports[`DependencyExtractionWebpackPlugin Webpack \`function-output-filename\` should produce expected output: Asset file 'chunk--main--main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); +" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); " `; @@ -73,7 +73,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`function-output-filename\` `; exports[`DependencyExtractionWebpackPlugin Webpack \`has-extension-suffix\` should produce expected output: Asset file 'index.min.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => 'dabeb91f3cb9dd73d48d'); +" array('lodash', 'wp-blob'), 'version' => '49dba68ef238f954b358'); " `; @@ -96,21 +96,21 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`has-extension-suffix\` shou `; exports[`DependencyExtractionWebpackPlugin Webpack \`no-default\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array(), 'version' => 'bb85a9737103c7054b00'); +" array(), 'version' => 'f7e2cb527e601f74f8bd'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`no-default\` should produce expected output: External modules should match snapshot 1`] = `[]`; exports[`DependencyExtractionWebpackPlugin Webpack \`no-deps\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array(), 'version' => '091ffcd70d94dd16e773'); +" array(), 'version' => '143ed23d4b8be5611fcb'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`no-deps\` should produce expected output: External modules should match snapshot 1`] = `[]`; exports[`DependencyExtractionWebpackPlugin Webpack \`option-function-output-filename\` should produce expected output: Asset file 'chunk--main--main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); +" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); " `; @@ -133,7 +133,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`option-function-output-file `; exports[`DependencyExtractionWebpackPlugin Webpack \`option-output-filename\` should produce expected output: Asset file 'main-foo.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); +" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); " `; @@ -155,7 +155,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`option-output-filename\` sh ] `; -exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should produce expected output: Asset file 'main.asset.json' should match snapshot 1`] = `"{"dependencies":["lodash"],"version":"a8f35bfc9f46482cc48a"}"`; +exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should produce expected output: Asset file 'main.asset.json' should match snapshot 1`] = `"{"dependencies":["lodash"],"version":"4c42b9646049ad2e9438"}"`; exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should produce expected output: External modules should match snapshot 1`] = ` [ @@ -168,7 +168,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should `; exports[`DependencyExtractionWebpackPlugin Webpack \`overrides\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('wp-blob', 'wp-script-handle-for-rxjs', 'wp-url'), 'version' => '2a29b245fc3d0509b5a8'); +" array('wp-blob', 'wp-script-handle-for-rxjs', 'wp-url'), 'version' => '708c71445153f1d07e4a'); " `; @@ -207,17 +207,17 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`overrides\` should produce `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'a.asset.php' should match snapshot 1`] = ` -" array('wp-blob'), 'version' => '4514ed711f6c035e0887'); +" array('wp-blob'), 'version' => '09a0c551770a351c5ca7'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'b.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '168d32b5fb42f9e5d8ce'); +" array('lodash', 'wp-blob'), 'version' => 'c9f00d690a9f72438910'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'runtime.asset.php' should match snapshot 1`] = ` -" array(), 'version' => 'd3c2ce2cb84ff74b92e0'); +" array(), 'version' => '46ea0ff11ac53fa5e88b'); " `; @@ -240,7 +240,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` shou `; exports[`DependencyExtractionWebpackPlugin Webpack \`style-imports\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '04b9da7eff6fbfcb0452'); +" array('lodash', 'wp-blob'), 'version' => 'd8c0ee89d933a3809c0e'); " `; @@ -263,7 +263,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`style-imports\` should prod `; exports[`DependencyExtractionWebpackPlugin Webpack \`wordpress\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); +" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); " `; @@ -286,7 +286,7 @@ exports[`DependencyExtractionWebpackPlugin Webpack \`wordpress\` should produce `; exports[`DependencyExtractionWebpackPlugin Webpack \`wordpress-require\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => 'ed2bd4e7df46768bb3c2'); +" array('lodash', 'wp-blob'), 'version' => '40370eb4ce6428562da6'); " `; diff --git a/packages/readable-js-assets-webpack-plugin/test/__snapshots__/build.js.snap b/packages/readable-js-assets-webpack-plugin/test/__snapshots__/build.js.snap index e5a7d6f6accc8..c033e572e8e5e 100644 --- a/packages/readable-js-assets-webpack-plugin/test/__snapshots__/build.js.snap +++ b/packages/readable-js-assets-webpack-plugin/test/__snapshots__/build.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ReadableJsAssetsWebpackPlugin should produce the expected output: Asset file index.js should match snapshot 1`] = ` -"/******/ (function() { // webpackBootstrap +"/******/ (() => { // webpackBootstrap var __webpack_exports__ = {}; function notMinified() { // eslint-disable-next-line no-console @@ -16,7 +16,7 @@ notMinified(); exports[`ReadableJsAssetsWebpackPlugin should produce the expected output: Asset file index.min.js should match snapshot 1`] = `"console.log("hello");"`; exports[`ReadableJsAssetsWebpackPlugin should produce the expected output: Asset file view.js should match snapshot 1`] = ` -"/******/ (function() { // webpackBootstrap +"/******/ (() => { // webpackBootstrap var __webpack_exports__ = {}; function notMinified() { // eslint-disable-next-line no-console From b0d20ed901c4f0580a47299f01c5f6aff71fa664 Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 18 May 2023 15:46:58 +1000 Subject: [PATCH 03/26] Global styles revisions: highlight currently-loaded revision (#50725) * Rename current to selected to reflect the usage Ensure the latest revision is highlighted even when there is no selected id Add useEffect dependencies Updating tests Align text to left for long strings * use a stable reference of an empty array --- .../global-styles/screen-revisions/index.js | 14 +---- .../screen-revisions/revisions-buttons.js | 58 +++++++++++-------- .../global-styles/screen-revisions/style.scss | 5 +- .../test/use-global-styles-revisions.js | 19 +++++- .../use-global-styles-revisions.js | 34 +++++------ 5 files changed, 70 insertions(+), 60 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/index.js b/packages/edit-site/src/components/global-styles/screen-revisions/index.js index 92d5ae7d2bb68..8a3e84e0ee41e 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/index.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/index.js @@ -42,19 +42,11 @@ function ScreenRevisions() { blocks: select( blockEditorStore ).getBlocks(), }; }, [] ); - const { revisions, isLoading, hasUnsavedChanges } = useGlobalStylesRevisions(); + const [ selectedRevisionId, setSelectedRevisionId ] = useState(); const [ globalStylesRevision, setGlobalStylesRevision ] = useState( userConfig ); - - const [ currentRevisionId, setCurrentRevisionId ] = useState( - /* - * We need this for the first render, - * otherwise the unsaved changes haven't been merged into the revisions array yet. - */ - hasUnsavedChanges ? 'unsaved' : revisions?.[ 0 ]?.id - ); const [ isLoadingRevisionWithUnsavedChanges, setIsLoadingRevisionWithUnsavedChanges, @@ -89,7 +81,7 @@ function ScreenRevisions() { settings: revision?.settings, id: revision?.id, } ); - setCurrentRevisionId( revision?.id ); + setSelectedRevisionId( revision?.id ); }; const isLoadButtonEnabled = @@ -117,7 +109,7 @@ function ScreenRevisions() {
{ isLoadButtonEnabled && ( diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js index c4d624bf1727e..f32441f6a41b0 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js @@ -18,9 +18,8 @@ import { dateI18n, getDate, humanTimeDiff, getSettings } from '@wordpress/date'; */ function getRevisionLabel( revision ) { const authorDisplayName = revision?.author?.name || __( 'User' ); - const isUnsaved = 'unsaved' === revision?.id; - if ( isUnsaved ) { + if ( 'unsaved' === revision?.id ) { return sprintf( /* translators: %(name)s author display name */ __( 'Unsaved changes by %(name)s' ), @@ -57,45 +56,42 @@ function getRevisionLabel( revision ) { * Returns a rendered list of revisions buttons. * * @typedef {Object} props - * @property {Array} userRevisions A collection of user revisions. - * @property {number} currentRevisionId Callback fired when the modal is closed or action cancelled. - * @property {Function} onChange Callback fired when a revision is selected. + * @property {Array} userRevisions A collection of user revisions. + * @property {number} selectedRevisionId The id of the currently-selected revision. + * @property {Function} onChange Callback fired when a revision is selected. * - * @param {props} Component props. + * @param {props} Component props. * @return {JSX.Element} The modal component. */ -function RevisionsButtons( { userRevisions, currentRevisionId, onChange } ) { +function RevisionsButtons( { userRevisions, selectedRevisionId, onChange } ) { return (
    - { userRevisions.map( ( revision ) => { - const { id, author, isLatest, modified } = revision; + { userRevisions.map( ( revision, index ) => { + const { id, author, modified } = revision; const authorDisplayName = author?.name || __( 'User' ); const authorAvatar = author?.avatar_urls?.[ '48' ]; - /* - * If the currentId hasn't been selected yet, the first revision is - * the current one so long as the API returns revisions in descending order. - */ - const isActive = !! currentRevisionId - ? id === currentRevisionId - : isLatest; + const isUnsaved = 'unsaved' === revision?.id; + const isSelected = selectedRevisionId + ? selectedRevisionId === revision?.id + : index === 0; return (
  1. - - ); -} - -export default forwardRef( ListViewBlockSelectButton ); diff --git a/packages/block-editor/src/components/off-canvas-editor/block.js b/packages/block-editor/src/components/off-canvas-editor/block.js deleted file mode 100644 index d5bc4e46c6ce9..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/block.js +++ /dev/null @@ -1,349 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { hasBlockSupport } from '@wordpress/blocks'; -import { - __experimentalTreeGridCell as TreeGridCell, - __experimentalTreeGridItem as TreeGridItem, -} from '@wordpress/components'; -import { useInstanceId } from '@wordpress/compose'; -import { moreVertical } from '@wordpress/icons'; -import { - useState, - useRef, - useEffect, - useCallback, - memo, -} from '@wordpress/element'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { sprintf, __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import ListViewLeaf from './leaf'; -import { - BlockMoverUpButton, - BlockMoverDownButton, -} from '../block-mover/button'; -import ListViewBlockContents from './block-contents'; -import BlockSettingsDropdown from '../block-settings-menu/block-settings-dropdown'; -import { useListViewContext } from './context'; -import { getBlockPositionDescription } from './utils'; -import { store as blockEditorStore } from '../../store'; -import useBlockDisplayInformation from '../use-block-display-information'; -import { useBlockLock } from '../block-lock'; - -function ListViewBlock( { - block: { clientId }, - isDragged, - isSelected, - isBranchSelected, - selectBlock, - position, - level, - rowCount, - siblingBlockCount, - showBlockMovers, - path, - isExpanded, - selectedClientIds, - preventAnnouncement, -} ) { - const cellRef = useRef( null ); - const [ isHovered, setIsHovered ] = useState( false ); - - const { isLocked, isContentLocked } = useBlockLock( clientId ); - const forceSelectionContentLock = useSelect( - ( select ) => { - if ( isSelected ) { - return false; - } - if ( ! isContentLocked ) { - return false; - } - return select( blockEditorStore ).hasSelectedInnerBlock( - clientId, - true - ); - }, - [ isContentLocked, clientId, isSelected ] - ); - - const isFirstSelectedBlock = - forceSelectionContentLock || - ( isSelected && selectedClientIds[ 0 ] === clientId ); - const isLastSelectedBlock = - forceSelectionContentLock || - ( isSelected && - selectedClientIds[ selectedClientIds.length - 1 ] === clientId ); - - const { toggleBlockHighlight } = useDispatch( blockEditorStore ); - - const blockInformation = useBlockDisplayInformation( clientId ); - const block = useSelect( - ( select ) => select( blockEditorStore ).getBlock( clientId ), - [ clientId ] - ); - - // If ListView has experimental features related to the Persistent List View, - // only focus the selected list item on mount; otherwise the list would always - // try to steal the focus from the editor canvas. - useEffect( () => { - if ( ! isTreeGridMounted && isSelected ) { - cellRef.current.focus(); - } - }, [] ); - - const onMouseEnter = useCallback( () => { - setIsHovered( true ); - toggleBlockHighlight( clientId, true ); - }, [ clientId, setIsHovered, toggleBlockHighlight ] ); - const onMouseLeave = useCallback( () => { - setIsHovered( false ); - toggleBlockHighlight( clientId, false ); - }, [ clientId, setIsHovered, toggleBlockHighlight ] ); - - const selectEditorBlock = useCallback( - ( event ) => { - selectBlock( event, clientId ); - event.preventDefault(); - }, - [ clientId, selectBlock ] - ); - - const updateSelection = useCallback( - ( newClientId ) => { - selectBlock( undefined, newClientId ); - }, - [ selectBlock ] - ); - - const { isTreeGridMounted, expand, expandedState, collapse, LeafMoreMenu } = - useListViewContext(); - - const toggleExpanded = useCallback( - ( event ) => { - // Prevent shift+click from opening link in a new window when toggling. - event.preventDefault(); - event.stopPropagation(); - if ( isExpanded === true ) { - collapse( clientId ); - } else if ( isExpanded === false ) { - expand( clientId ); - } - }, - [ clientId, expand, collapse, isExpanded ] - ); - - const instanceId = useInstanceId( ListViewBlock ); - - if ( ! block ) { - return null; - } - - // When a block hides its toolbar it also hides the block settings menu, - // since that menu is part of the toolbar in the editor canvas. - // List View respects this by also hiding the block settings menu. - const showBlockActions = - !! block && - hasBlockSupport( block.name, '__experimentalToolbar', true ); - - const descriptionId = `list-view-block-select-button__${ instanceId }`; - const blockPositionDescription = getBlockPositionDescription( - position, - siblingBlockCount, - level - ); - - let blockAriaLabel = __( 'Link' ); - if ( blockInformation ) { - blockAriaLabel = isLocked - ? sprintf( - // translators: %s: The title of the block. This string indicates a link to select the locked block. - __( '%s link (locked)' ), - blockInformation.title - ) - : sprintf( - // translators: %s: The title of the block. This string indicates a link to select the block. - __( '%s link' ), - blockInformation.title - ); - } - - const settingsAriaLabel = blockInformation - ? sprintf( - // translators: %s: The title of the block. - __( 'Options for %s block' ), - blockInformation.title - ) - : __( 'Options' ); - - const hasSiblings = siblingBlockCount > 0; - const hasRenderedMovers = showBlockMovers && hasSiblings; - const moverCellClassName = classnames( - 'block-editor-list-view-block__mover-cell', - { 'is-visible': isHovered || isSelected } - ); - - const listViewBlockSettingsClassName = classnames( - 'block-editor-list-view-block__menu-cell', - { 'is-visible': isHovered || isFirstSelectedBlock } - ); - - let colSpan; - if ( hasRenderedMovers ) { - colSpan = 1; - } else if ( ! showBlockActions ) { - colSpan = 2; - } - - const classes = classnames( { - 'is-selected': isSelected || forceSelectionContentLock, - 'is-first-selected': isFirstSelectedBlock, - 'is-last-selected': isLastSelectedBlock, - 'is-branch-selected': isBranchSelected, - 'is-dragging': isDragged, - 'has-single-cell': ! showBlockActions, - } ); - - // Only include all selected blocks if the currently clicked on block - // is one of the selected blocks. This ensures that if a user attempts - // to alter a block that isn't part of the selection, they're still able - // to do so. - const dropdownClientIds = selectedClientIds.includes( clientId ) - ? selectedClientIds - : [ clientId ]; - - const MoreMenuComponent = LeafMoreMenu - ? LeafMoreMenu - : BlockSettingsDropdown; - - return ( - - - { ( { ref, tabIndex, onFocus } ) => ( -
    - -
    - { blockPositionDescription } -
    -
    - ) } -
    - { hasRenderedMovers && ( - <> - - - { ( { ref, tabIndex, onFocus } ) => ( - - ) } - - - { ( { ref, tabIndex, onFocus } ) => ( - - ) } - - - - ) } - - { showBlockActions && ( - <> - - { ( { ref, tabIndex, onFocus } ) => ( - <> - - - ) } - - - ) } -
    - ); -} - -export default memo( ListViewBlock ); diff --git a/packages/block-editor/src/components/off-canvas-editor/branch.js b/packages/block-editor/src/components/off-canvas-editor/branch.js deleted file mode 100644 index 3749ef65add49..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/branch.js +++ /dev/null @@ -1,238 +0,0 @@ -/** - * WordPress dependencies - */ -import { - __experimentalTreeGridRow as TreeGridRow, - __experimentalTreeGridCell as TreeGridCell, -} from '@wordpress/components'; -import { AsyncModeProvider, useSelect } from '@wordpress/data'; -import { memo } from '@wordpress/element'; - -/** - * Internal dependencies - */ - -import { Appender } from './appender'; -import ListViewBlock from './block'; -import { useListViewContext } from './context'; -import { isClientIdSelected } from './utils'; -import { store as blockEditorStore } from '../../store'; - -/** - * Given a block, returns the total number of blocks in that subtree. This is used to help determine - * the list position of a block. - * - * When a block is collapsed, we do not count their children as part of that total. In the current drag - * implementation dragged blocks and their children are not counted. - * - * @param {Object} block block tree - * @param {Object} expandedState state that notes which branches are collapsed - * @param {Array} draggedClientIds a list of dragged client ids - * @param {boolean} isExpandedByDefault flag to determine the default fallback expanded state. - * @return {number} block count - */ -function countBlocks( - block, - expandedState, - draggedClientIds, - isExpandedByDefault -) { - const isDragged = draggedClientIds?.includes( block.clientId ); - if ( isDragged ) { - return 0; - } - const isExpanded = expandedState[ block.clientId ] ?? isExpandedByDefault; - - if ( isExpanded ) { - return ( - 1 + - block.innerBlocks.reduce( - countReducer( - expandedState, - draggedClientIds, - isExpandedByDefault - ), - 0 - ) - ); - } - return 1; -} -const countReducer = - ( expandedState, draggedClientIds, isExpandedByDefault ) => - ( count, block ) => { - const isDragged = draggedClientIds?.includes( block.clientId ); - if ( isDragged ) { - return count; - } - const isExpanded = - expandedState[ block.clientId ] ?? isExpandedByDefault; - if ( isExpanded && block.innerBlocks.length > 0 ) { - return ( - count + - countBlocks( - block, - expandedState, - draggedClientIds, - isExpandedByDefault - ) - ); - } - return count + 1; - }; - -const noop = () => {}; - -function ListViewBranch( props ) { - const { - blocks, - selectBlock = noop, - showBlockMovers, - selectedClientIds, - level = 1, - path = '', - isBranchSelected = false, - listPosition = 0, - fixedListWindow, - isExpanded, - parentId, - shouldShowInnerBlocks = true, - showAppender: showAppenderProp = true, - } = props; - - const isContentLocked = useSelect( - ( select ) => { - return !! ( - parentId && - select( blockEditorStore ).getTemplateLock( parentId ) === - 'contentOnly' - ); - }, - [ parentId ] - ); - - const { expandedState, draggedClientIds } = useListViewContext(); - - if ( isContentLocked ) { - return null; - } - - // Only show the appender at the first level. - const showAppender = showAppenderProp && level === 1; - - const filteredBlocks = blocks.filter( Boolean ); - const blockCount = filteredBlocks.length; - - // The appender means an extra row in List View, so add 1 to the row count. - const rowCount = showAppender ? blockCount + 1 : blockCount; - let nextPosition = listPosition; - - return ( - <> - { filteredBlocks.map( ( block, index ) => { - const { clientId, innerBlocks } = block; - - if ( index > 0 ) { - nextPosition += countBlocks( - filteredBlocks[ index - 1 ], - expandedState, - draggedClientIds, - isExpanded - ); - } - - const { itemInView } = fixedListWindow; - const blockInView = itemInView( nextPosition ); - - const position = index + 1; - const updatedPath = - path.length > 0 - ? `${ path }_${ position }` - : `${ position }`; - const hasNestedBlocks = !! innerBlocks?.length; - - const shouldExpand = - hasNestedBlocks && shouldShowInnerBlocks - ? expandedState[ clientId ] ?? isExpanded - : undefined; - - const isDragged = !! draggedClientIds?.includes( clientId ); - - const showBlock = isDragged || blockInView; - - // Make updates to the selected or dragged blocks synchronous, - // but asynchronous for any other block. - const isSelected = isClientIdSelected( - clientId, - selectedClientIds - ); - const isSelectedBranch = - isBranchSelected || ( isSelected && hasNestedBlocks ); - return ( - - { showBlock && ( - - ) } - { ! showBlock && ( - - - - ) } - { hasNestedBlocks && shouldExpand && ! isDragged && ( - - ) } - - ); - } ) } - { showAppender && ( - - - { ( treeGridCellProps ) => ( - - ) } - - - ) } - - ); -} - -export default memo( ListViewBranch ); diff --git a/packages/block-editor/src/components/off-canvas-editor/context.js b/packages/block-editor/src/components/off-canvas-editor/context.js deleted file mode 100644 index c837dce9ca23f..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/context.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * WordPress dependencies - */ -import { createContext, useContext } from '@wordpress/element'; - -export const ListViewContext = createContext( {} ); - -export const useListViewContext = () => useContext( ListViewContext ); diff --git a/packages/block-editor/src/components/off-canvas-editor/drop-indicator.js b/packages/block-editor/src/components/off-canvas-editor/drop-indicator.js deleted file mode 100644 index 1e8d51a73919a..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/drop-indicator.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * WordPress dependencies - */ -import { Popover } from '@wordpress/components'; -import { useCallback, useMemo } from '@wordpress/element'; - -export default function ListViewDropIndicator( { - listViewRef, - blockDropTarget, -} ) { - const { rootClientId, clientId, dropPosition } = blockDropTarget || {}; - - const [ rootBlockElement, blockElement ] = useMemo( () => { - if ( ! listViewRef.current ) { - return []; - } - - // The rootClientId will be defined whenever dropping into inner - // block lists, but is undefined when dropping at the root level. - const _rootBlockElement = rootClientId - ? listViewRef.current.querySelector( - `[data-block="${ rootClientId }"]` - ) - : undefined; - - // The clientId represents the sibling block, the dragged block will - // usually be inserted adjacent to it. It will be undefined when - // dropping a block into an empty block list. - const _blockElement = clientId - ? listViewRef.current.querySelector( - `[data-block="${ clientId }"]` - ) - : undefined; - - return [ _rootBlockElement, _blockElement ]; - }, [ rootClientId, clientId ] ); - - // The targetElement is the element that the drop indicator will appear - // before or after. When dropping into an empty block list, blockElement - // is undefined, so the indicator will appear after the rootBlockElement. - const targetElement = blockElement || rootBlockElement; - - const getDropIndicatorIndent = useCallback( () => { - if ( ! rootBlockElement ) { - return 0; - } - - // Calculate the indent using the block icon of the root block. - // Using a classname selector here might be flaky and could be - // improved. - const targetElementRect = targetElement.getBoundingClientRect(); - const rootBlockIconElement = rootBlockElement.querySelector( - '.block-editor-block-icon' - ); - const rootBlockIconRect = rootBlockIconElement.getBoundingClientRect(); - return rootBlockIconRect.right - targetElementRect.left; - }, [ rootBlockElement, targetElement ] ); - - const style = useMemo( () => { - if ( ! targetElement ) { - return {}; - } - - const indent = getDropIndicatorIndent(); - - return { - width: targetElement.offsetWidth - indent, - }; - }, [ getDropIndicatorIndent, targetElement ] ); - - const popoverAnchor = useMemo( () => { - const isValidDropPosition = - dropPosition === 'top' || - dropPosition === 'bottom' || - dropPosition === 'inside'; - if ( ! targetElement || ! isValidDropPosition ) { - return undefined; - } - - return { - ownerDocument: targetElement.ownerDocument, - getBoundingClientRect() { - const rect = targetElement.getBoundingClientRect(); - const indent = getDropIndicatorIndent(); - - const left = rect.left + indent; - const right = rect.right; - let top = 0; - let bottom = 0; - - if ( dropPosition === 'top' ) { - top = rect.top; - bottom = rect.top; - } else { - // `dropPosition` is either `bottom` or `inside` - top = rect.bottom; - bottom = rect.bottom; - } - - const width = right - left; - const height = bottom - top; - - return new window.DOMRect( left, top, width, height ); - }, - }; - }, [ targetElement, dropPosition, getDropIndicatorIndent ] ); - - if ( ! targetElement ) { - return null; - } - - return ( - -
    - - ); -} diff --git a/packages/block-editor/src/components/off-canvas-editor/expander.js b/packages/block-editor/src/components/off-canvas-editor/expander.js deleted file mode 100644 index 3b93f8ad01185..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/expander.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * WordPress dependencies - */ -import { chevronRightSmall, chevronLeftSmall, Icon } from '@wordpress/icons'; -import { isRTL } from '@wordpress/i18n'; - -export default function ListViewExpander( { onClick } ) { - return ( - // Keyboard events are handled by TreeGrid see: components/src/tree-grid/index.js - // - // The expander component is implemented as a pseudo element in the w3 example - // https://www.w3.org/TR/wai-aria-practices/examples/treegrid/treegrid-1.html - // - // We've mimicked this by adding an icon with aria-hidden set to true to hide this from the accessibility tree. - // For the current tree grid implementation, please do not try to make this a button. - // - // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions - onClick( event, { forceToggle: true } ) } - aria-hidden="true" - > - - - ); -} diff --git a/packages/block-editor/src/components/off-canvas-editor/index.js b/packages/block-editor/src/components/off-canvas-editor/index.js deleted file mode 100644 index 97b8a4d4a71c5..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/index.js +++ /dev/null @@ -1,271 +0,0 @@ -/** - * WordPress dependencies - */ -import { - useMergeRefs, - __experimentalUseFixedWindowList as useFixedWindowList, -} from '@wordpress/compose'; -import { - __experimentalTreeGrid as TreeGrid, - __experimentalTreeGridRow as TreeGridRow, - __experimentalTreeGridCell as TreeGridCell, -} from '@wordpress/components'; -import { AsyncModeProvider, useSelect } from '@wordpress/data'; -import { - useCallback, - useEffect, - useMemo, - useRef, - useReducer, - forwardRef, -} from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import ListViewBranch from './branch'; -import { ListViewContext } from './context'; -import ListViewDropIndicator from './drop-indicator'; -import useBlockSelection from './use-block-selection'; -import useListViewClientIds from './use-list-view-client-ids'; -import useListViewDropZone from './use-list-view-drop-zone'; -import useListViewExpandSelectedItem from './use-list-view-expand-selected-item'; -import { store as blockEditorStore } from '../../store'; - -const expanded = ( state, action ) => { - if ( Array.isArray( action.clientIds ) ) { - return { - ...state, - ...action.clientIds.reduce( - ( newState, id ) => ( { - ...newState, - [ id ]: action.type === 'expand', - } ), - {} - ), - }; - } - return state; -}; - -export const BLOCK_LIST_ITEM_HEIGHT = 36; - -/** - * Show a hierarchical list of blocks. - * - * @param {Object} props Components props. - * @param {string} props.id An HTML element id for the root element of ListView. - * @param {string} props.parentClientId The client id of the parent block. - * @param {Array} props.blocks Custom subset of block client IDs to be used instead of the default hierarchy. - * @param {boolean} props.showBlockMovers Flag to enable block movers - * @param {boolean} props.isExpanded Flag to determine whether nested levels are expanded by default. - * @param {Object} props.LeafMoreMenu Optional more menu substitution. - * @param {string} props.description Optional accessible description for the tree grid component. - * @param {string} props.onSelect Optional callback to be invoked when a block is selected. - * @param {string} props.showAppender Flag to show or hide the block appender. - * @param {Function} props.renderAdditionalBlockUI Function that renders additional block content UI. - * @param {Object} ref Forwarded ref. - */ -function OffCanvasEditor( - { - id, - parentClientId, - blocks, - showBlockMovers = false, - isExpanded = false, - showAppender = true, - LeafMoreMenu, - description = __( 'Block navigation structure' ), - onSelect, - renderAdditionalBlockUI, - }, - ref -) { - const { getBlock } = useSelect( blockEditorStore ); - const { clientIdsTree, draggedClientIds, selectedClientIds } = - useListViewClientIds( blocks ); - - const { visibleBlockCount, shouldShowInnerBlocks } = useSelect( - ( select ) => { - const { - getGlobalBlockCount, - getClientIdsOfDescendants, - __unstableGetEditorMode, - } = select( blockEditorStore ); - const draggedBlockCount = - draggedClientIds?.length > 0 - ? getClientIdsOfDescendants( draggedClientIds ).length + 1 - : 0; - return { - visibleBlockCount: getGlobalBlockCount() - draggedBlockCount, - shouldShowInnerBlocks: __unstableGetEditorMode() !== 'zoom-out', - }; - }, - [ draggedClientIds, blocks ] - ); - - const { updateBlockSelection } = useBlockSelection(); - - const [ expandedState, setExpandedState ] = useReducer( expanded, {} ); - - const { ref: dropZoneRef, target: blockDropTarget } = useListViewDropZone(); - const elementRef = useRef(); - const treeGridRef = useMergeRefs( [ elementRef, dropZoneRef, ref ] ); - - const isMounted = useRef( false ); - const { setSelectedTreeId } = useListViewExpandSelectedItem( { - firstSelectedBlockClientId: selectedClientIds[ 0 ], - setExpandedState, - } ); - const selectEditorBlock = useCallback( - ( event, blockClientId ) => { - updateBlockSelection( event, blockClientId ); - setSelectedTreeId( blockClientId ); - if ( onSelect ) { - onSelect( getBlock( blockClientId ) ); - } - }, - [ setSelectedTreeId, updateBlockSelection, onSelect, getBlock ] - ); - useEffect( () => { - isMounted.current = true; - }, [] ); - - // List View renders a fixed number of items and relies on each having a fixed item height of 36px. - // If this value changes, we should also change the itemHeight value set in useFixedWindowList. - // See: https://github.com/WordPress/gutenberg/pull/35230 for additional context. - const [ fixedListWindow ] = useFixedWindowList( - elementRef, - BLOCK_LIST_ITEM_HEIGHT, - visibleBlockCount, - { - useWindowing: true, - windowOverscan: 40, - } - ); - - const expand = useCallback( - ( blockClientId ) => { - if ( ! blockClientId ) { - return; - } - setExpandedState( { - type: 'expand', - clientIds: [ blockClientId ], - } ); - }, - [ setExpandedState ] - ); - const collapse = useCallback( - ( blockClientId ) => { - if ( ! blockClientId ) { - return; - } - setExpandedState( { - type: 'collapse', - clientIds: [ blockClientId ], - } ); - }, - [ setExpandedState ] - ); - const expandRow = useCallback( - ( row ) => { - expand( row?.dataset?.block ); - }, - [ expand ] - ); - const collapseRow = useCallback( - ( row ) => { - collapse( row?.dataset?.block ); - }, - [ collapse ] - ); - const focusRow = useCallback( - ( event, startRow, endRow ) => { - if ( event.shiftKey ) { - updateBlockSelection( - event, - startRow?.dataset?.block, - endRow?.dataset?.block - ); - } - }, - [ updateBlockSelection ] - ); - - const contextValue = useMemo( - () => ( { - isTreeGridMounted: isMounted.current, - draggedClientIds, - expandedState, - expand, - collapse, - LeafMoreMenu, - renderAdditionalBlockUI, - } ), - [ - isMounted.current, - draggedClientIds, - expandedState, - expand, - collapse, - LeafMoreMenu, - renderAdditionalBlockUI, - ] - ); - - return ( - - -
    - - - - - { ! clientIdsTree.length && ( - -
    - { __( - 'Your menu is currently empty. Add your first menu item to get started.' - ) } -
    -
    - ) } -
    -
    -
    -
    -
    - ); -} - -export default forwardRef( OffCanvasEditor ); diff --git a/packages/block-editor/src/components/off-canvas-editor/leaf.js b/packages/block-editor/src/components/off-canvas-editor/leaf.js deleted file mode 100644 index 7d74c85ffeb36..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/leaf.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * External dependencies - */ -import { animated } from '@react-spring/web'; -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { __experimentalTreeGridRow as TreeGridRow } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import useMovingAnimation from '../use-moving-animation'; - -const AnimatedTreeGridRow = animated( TreeGridRow ); - -export default function ListViewLeaf( { - isSelected, - position, - level, - rowCount, - children, - className, - path, - ...props -} ) { - const ref = useMovingAnimation( { - isSelected, - adjustScrolling: false, - enableAnimation: true, - triggerAnimationOnChange: path, - } ); - - return ( - - { children } - - ); -} diff --git a/packages/block-editor/src/components/off-canvas-editor/link-ui.js b/packages/block-editor/src/components/off-canvas-editor/link-ui.js deleted file mode 100644 index f6b5e2538d9e7..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/link-ui.js +++ /dev/null @@ -1,167 +0,0 @@ -// Note: this file is copied directly from packages/block-library/src/navigation-link/link-ui.js - -/** - * WordPress dependencies - */ -import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; -import { Popover, Button } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { switchToBlockType } from '@wordpress/blocks'; -import { useSelect, useDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as blockEditorStore } from '../../store'; -import LinkControl from '../link-control'; -import BlockIcon from '../block-icon'; - -/** - * Given the Link block's type attribute, return the query params to give to - * /wp/v2/search. - * - * @param {string} type Link block's type attribute. - * @param {string} kind Link block's entity of kind (post-type|taxonomy) - * @return {{ type?: string, subtype?: string }} Search query params. - */ -export function getSuggestionsQuery( type, kind ) { - switch ( type ) { - case 'post': - case 'page': - return { type: 'post', subtype: type }; - case 'category': - return { type: 'term', subtype: 'category' }; - case 'tag': - return { type: 'term', subtype: 'post_tag' }; - case 'post_format': - return { type: 'post-format' }; - default: - if ( kind === 'taxonomy' ) { - return { type: 'term', subtype: type }; - } - if ( kind === 'post-type' ) { - return { type: 'post', subtype: type }; - } - return {}; - } -} - -/** - * Add transforms to Link Control - * - * @param {Object} props Component props. - * @param {string} props.clientId Block client ID. - */ -function LinkControlTransforms( { clientId } ) { - const { getBlock, blockTransforms } = useSelect( - ( select ) => { - const { - getBlock: _getBlock, - getBlockRootClientId, - getBlockTransformItems, - } = select( blockEditorStore ); - - return { - getBlock: _getBlock, - blockTransforms: getBlockTransformItems( - _getBlock( clientId ), - getBlockRootClientId( clientId ) - ), - }; - }, - [ clientId ] - ); - - const { replaceBlock } = useDispatch( blockEditorStore ); - - const featuredBlocks = [ - 'core/page-list', - 'core/site-logo', - 'core/social-links', - 'core/search', - ]; - - const transforms = blockTransforms.filter( ( item ) => { - return featuredBlocks.includes( item.name ); - } ); - - if ( ! transforms?.length ) { - return null; - } - - if ( ! clientId ) { - return null; - } - - return ( -
    -

    - { __( 'Transform' ) } -

    -
    - { transforms.map( ( item ) => { - return ( - - ); - } ) } -
    -
    - ); -} - -export function LinkUI( props ) { - const { label, url, opensInNewTab, type, kind } = props.link; - const link = { - url, - opensInNewTab, - title: label && stripHTML( label ), - }; - - return ( - - ( - - ) - : null - } - /> - - ); -} diff --git a/packages/block-editor/src/components/off-canvas-editor/style.scss b/packages/block-editor/src/components/off-canvas-editor/style.scss deleted file mode 100644 index 6cf9f312265e3..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/style.scss +++ /dev/null @@ -1,34 +0,0 @@ -.offcanvas-editor-appender .block-editor-inserter__toggle { - background-color: #1e1e1e; - color: #fff; - margin: $grid-unit-10 0 0 24px; - border-radius: 2px; - height: 24px; - min-width: 24px; - padding: 0; - - &:hover, - &:focus { - background: var(--wp-admin-theme-color); - color: #fff; - } -} - -.offcanvas-editor-appender__description { - display: none; -} - -.offcanvas-editor-list-view-tree-wrapper { - max-width: 100%; - overflow-x: auto; -} - -.offcanvas-editor-list-view-leaf { - display: block; - // sidebar width - tab panel padding - max-width: $sidebar-width - (2 * $grid-unit-20); -} - -.offcanvas-editor-list-view-is-empty { - margin-left: $grid-unit-20; -} diff --git a/packages/block-editor/src/components/off-canvas-editor/test/use-inserted-block.js b/packages/block-editor/src/components/off-canvas-editor/test/use-inserted-block.js deleted file mode 100644 index f4e6746581e00..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/test/use-inserted-block.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Internal dependencies - */ -import { useInsertedBlock } from '../use-inserted-block'; - -/** - * WordPress dependencies - */ -import { useDispatch, useSelect } from '@wordpress/data'; - -/** - * External dependencies - */ -import { act, renderHook } from '@testing-library/react'; - -jest.mock( '@wordpress/data/src/components/use-select', () => { - // This allows us to tweak the returned value on each test. - const mock = jest.fn(); - return mock; -} ); - -jest.mock( '@wordpress/data/src/components/use-dispatch', () => ( { - useDispatch: jest.fn(), -} ) ); - -describe( 'useInsertedBlock', () => { - const mockUpdateBlockAttributes = jest.fn(); - - it( 'returns undefined values when called without a block clientId', () => { - useSelect.mockImplementation( () => ( { - insertedBlockAttributes: { - 'some-attribute': 'some-value', - }, - insertedBlockName: 'core/navigation-link', - } ) ); - - useDispatch.mockImplementation( () => ( { - updateBlockAttributes: mockUpdateBlockAttributes, - } ) ); - - const { result } = renderHook( () => useInsertedBlock() ); - - const { - insertedBlockName, - insertedBlockAttributes, - setInsertedBlockAttributes, - } = result.current; - - expect( insertedBlockName ).toBeUndefined(); - expect( insertedBlockAttributes ).toBeUndefined(); - expect( - setInsertedBlockAttributes( { 'some-attribute': 'new-value' } ) - ).toBeUndefined(); - } ); - - it( 'returns name and attributes when called with a block clientId', () => { - useSelect.mockImplementation( () => ( { - insertedBlockAttributes: { - 'some-attribute': 'some-value', - }, - insertedBlockName: 'core/navigation-link', - } ) ); - - useDispatch.mockImplementation( () => ( { - updateBlockAttributes: mockUpdateBlockAttributes, - } ) ); - - const { result } = renderHook( () => - useInsertedBlock( 'some-client-id-here' ) - ); - - const { insertedBlockName, insertedBlockAttributes } = result.current; - - expect( insertedBlockName ).toBe( 'core/navigation-link' ); - expect( insertedBlockAttributes ).toEqual( { - 'some-attribute': 'some-value', - } ); - } ); - - it( 'dispatches updateBlockAttributes on provided client ID with new attributes when setInsertedBlockAttributes is called', () => { - useSelect.mockImplementation( () => ( { - insertedBlockAttributes: { - 'some-attribute': 'some-value', - }, - insertedBlockName: 'core/navigation-link', - } ) ); - - useDispatch.mockImplementation( () => ( { - updateBlockAttributes: mockUpdateBlockAttributes, - } ) ); - - const clientId = '123456789'; - - const { result } = renderHook( () => useInsertedBlock( clientId ) ); - - const { setInsertedBlockAttributes } = result.current; - - act( () => { - setInsertedBlockAttributes( { - 'some-attribute': 'new-value', - } ); - } ); - - expect( mockUpdateBlockAttributes ).toHaveBeenCalledWith( clientId, { - 'some-attribute': 'new-value', - } ); - } ); -} ); diff --git a/packages/block-editor/src/components/off-canvas-editor/test/utils.js b/packages/block-editor/src/components/off-canvas-editor/test/utils.js deleted file mode 100644 index 78d78a9d90069..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/test/utils.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Internal dependencies - */ -import { getCommonDepthClientIds } from '../utils'; - -describe( 'getCommonDepthClientIds', () => { - it( 'should return start and end when no depth is provided', () => { - const result = getCommonDepthClientIds( - 'start-id', - 'clicked-id', - [], - [] - ); - - expect( result ).toEqual( { start: 'start-id', end: 'clicked-id' } ); - } ); - - it( 'should return deepest start and end when depths match', () => { - const result = getCommonDepthClientIds( - 'start-id', - 'clicked-id', - [ 'start-1', 'start-2', 'start-3' ], - [ 'end-1', 'end-2', 'end-3' ] - ); - - expect( result ).toEqual( { start: 'start-id', end: 'clicked-id' } ); - } ); - - it( 'should return shallower ids when start is shallower', () => { - const result = getCommonDepthClientIds( - 'start-id', - 'clicked-id', - [ 'start-1' ], - [ 'end-1', 'end-2', 'end-3' ] - ); - - expect( result ).toEqual( { start: 'start-id', end: 'end-2' } ); - } ); - - it( 'should return shallower ids when end is shallower', () => { - const result = getCommonDepthClientIds( - 'start-id', - 'clicked-id', - [ 'start-1', 'start-2', 'start-3' ], - [ 'end-1', 'end-2' ] - ); - - expect( result ).toEqual( { start: 'start-3', end: 'clicked-id' } ); - } ); -} ); diff --git a/packages/block-editor/src/components/off-canvas-editor/update-attributes.js b/packages/block-editor/src/components/off-canvas-editor/update-attributes.js deleted file mode 100644 index 5133cae387833..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/update-attributes.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * WordPress dependencies - */ -import { escapeHTML } from '@wordpress/escape-html'; -import { safeDecodeURI } from '@wordpress/url'; - -/** - * @typedef {'post-type'|'custom'|'taxonomy'|'post-type-archive'} WPNavigationLinkKind - */ -/** - * Navigation Link Block Attributes - * - * @typedef {Object} WPNavigationLinkBlockAttributes - * - * @property {string} [label] Link text. - * @property {WPNavigationLinkKind} [kind] Kind is used to differentiate between term and post ids to check post draft status. - * @property {string} [type] The type such as post, page, tag, category and other custom types. - * @property {string} [rel] The relationship of the linked URL. - * @property {number} [id] A post or term id. - * @property {boolean} [opensInNewTab] Sets link target to _blank when true. - * @property {string} [url] Link href. - * @property {string} [title] Link title attribute. - */ -/** - * Link Control onChange handler that updates block attributes when a setting is changed. - * - * @param {Object} updatedValue New block attributes to update. - * @param {Function} setAttributes Block attribute update function. - * @param {WPNavigationLinkBlockAttributes} blockAttributes Current block attributes. - * - */ - -export const updateAttributes = ( - updatedValue = {}, - setAttributes, - blockAttributes = {} -) => { - const { - label: originalLabel = '', - kind: originalKind = '', - type: originalType = '', - } = blockAttributes; - - const { - title: newLabel = '', // the title of any provided Post. - url: newUrl = '', - opensInNewTab, - id, - kind: newKind = originalKind, - type: newType = originalType, - } = updatedValue; - - const newLabelWithoutHttp = newLabel.replace( /http(s?):\/\//gi, '' ); - const newUrlWithoutHttp = newUrl.replace( /http(s?):\/\//gi, '' ); - - const useNewLabel = - newLabel && - newLabel !== originalLabel && - // LinkControl without the title field relies - // on the check below. Specifically, it assumes that - // the URL is the same as a title. - // This logic a) looks suspicious and b) should really - // live in the LinkControl and not here. It's a great - // candidate for future refactoring. - newLabelWithoutHttp !== newUrlWithoutHttp; - - // Unfortunately this causes the escaping model to be inverted. - // The escaped content is stored in the block attributes (and ultimately in the database), - // and then the raw data is "recovered" when outputting into the DOM. - // It would be preferable to store the **raw** data in the block attributes and escape it in JS. - // Why? Because there isn't one way to escape data. Depending on the context, you need to do - // different transforms. It doesn't make sense to me to choose one of them for the purposes of storage. - // See also: - // - https://github.com/WordPress/gutenberg/pull/41063 - // - https://github.com/WordPress/gutenberg/pull/18617. - const label = useNewLabel - ? escapeHTML( newLabel ) - : originalLabel || escapeHTML( newUrlWithoutHttp ); - - // In https://github.com/WordPress/gutenberg/pull/24670 we decided to use "tag" in favor of "post_tag" - const type = newType === 'post_tag' ? 'tag' : newType.replace( '-', '_' ); - - const isBuiltInType = - [ 'post', 'page', 'tag', 'category' ].indexOf( type ) > -1; - - const isCustomLink = - ( ! newKind && ! isBuiltInType ) || newKind === 'custom'; - const kind = isCustomLink ? 'custom' : newKind; - - setAttributes( { - // Passed `url` may already be encoded. To prevent double encoding, decodeURI is executed to revert to the original string. - ...( newUrl && { url: encodeURI( safeDecodeURI( newUrl ) ) } ), - ...( label && { label } ), - ...( undefined !== opensInNewTab && { opensInNewTab } ), - ...( id && Number.isInteger( id ) && { id } ), - ...( kind && { kind } ), - ...( type && type !== 'URL' && { type } ), - } ); -}; diff --git a/packages/block-editor/src/components/off-canvas-editor/use-block-selection.js b/packages/block-editor/src/components/off-canvas-editor/use-block-selection.js deleted file mode 100644 index 59aaaeacb01d4..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/use-block-selection.js +++ /dev/null @@ -1,169 +0,0 @@ -/** - * WordPress dependencies - */ -import { speak } from '@wordpress/a11y'; -import { __, sprintf } from '@wordpress/i18n'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { useCallback } from '@wordpress/element'; -import { UP, DOWN, HOME, END } from '@wordpress/keycodes'; -import { store as blocksStore } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import { store as blockEditorStore } from '../../store'; -import { getCommonDepthClientIds } from './utils'; - -export default function useBlockSelection() { - const { clearSelectedBlock, multiSelect, selectBlock } = - useDispatch( blockEditorStore ); - const { - getBlockName, - getBlockParents, - getBlockSelectionStart, - getBlockSelectionEnd, - getSelectedBlockClientIds, - hasMultiSelection, - hasSelectedBlock, - } = useSelect( blockEditorStore ); - - const { getBlockType } = useSelect( blocksStore ); - - const updateBlockSelection = useCallback( - async ( event, clientId, destinationClientId ) => { - if ( ! event?.shiftKey ) { - selectBlock( clientId ); - return; - } - - // To handle multiple block selection via the `SHIFT` key, prevent - // the browser default behavior of opening the link in a new window. - event.preventDefault(); - - const isKeyPress = - event.type === 'keydown' && - ( event.keyCode === UP || - event.keyCode === DOWN || - event.keyCode === HOME || - event.keyCode === END ); - - // Handle clicking on a block when no blocks are selected, and return early. - if ( - ! isKeyPress && - ! hasSelectedBlock() && - ! hasMultiSelection() - ) { - selectBlock( clientId, null ); - return; - } - - const selectedBlocks = getSelectedBlockClientIds(); - const clientIdWithParents = [ - ...getBlockParents( clientId ), - clientId, - ]; - - if ( - isKeyPress && - ! selectedBlocks.some( ( blockId ) => - clientIdWithParents.includes( blockId ) - ) - ) { - // Ensure that shift-selecting blocks via the keyboard only - // expands the current selection if focusing over already - // selected blocks. Otherwise, clear the selection so that - // a user can create a new selection entirely by keyboard. - await clearSelectedBlock(); - } - - let startTarget = getBlockSelectionStart(); - let endTarget = clientId; - - // Handle keyboard behavior for selecting multiple blocks. - if ( isKeyPress ) { - if ( ! hasSelectedBlock() && ! hasMultiSelection() ) { - // Set the starting point of the selection to the currently - // focused block, if there are no blocks currently selected. - // This ensures that as the selection is expanded or contracted, - // the starting point of the selection is anchored to that block. - startTarget = clientId; - } - if ( destinationClientId ) { - // If the user presses UP or DOWN, we want to ensure that the block they're - // moving to is the target for selection, and not the currently focused one. - endTarget = destinationClientId; - } - } - - const startParents = getBlockParents( startTarget ); - const endParents = getBlockParents( endTarget ); - - const { start, end } = getCommonDepthClientIds( - startTarget, - endTarget, - startParents, - endParents - ); - await multiSelect( start, end, null ); - - // Announce deselected block, or number of deselected blocks if - // the total number of blocks deselected is greater than one. - const updatedSelectedBlocks = getSelectedBlockClientIds(); - - // If the selection is greater than 1 and the Home or End keys - // were used to generate the selection, then skip announcing the - // deselected blocks. - if ( - ( event.keyCode === HOME || event.keyCode === END ) && - updatedSelectedBlocks.length > 1 - ) { - return; - } - - const selectionDiff = selectedBlocks.filter( - ( blockId ) => ! updatedSelectedBlocks.includes( blockId ) - ); - - let label; - if ( selectionDiff.length === 1 ) { - const title = getBlockType( - getBlockName( selectionDiff[ 0 ] ) - )?.title; - if ( title ) { - label = sprintf( - /* translators: %s: block name */ - __( '%s deselected.' ), - title - ); - } - } else if ( selectionDiff.length > 1 ) { - label = sprintf( - /* translators: %s: number of deselected blocks */ - __( '%s blocks deselected.' ), - selectionDiff.length - ); - } - - if ( label ) { - speak( label ); - } - }, - [ - clearSelectedBlock, - getBlockName, - getBlockType, - getBlockParents, - getBlockSelectionStart, - getBlockSelectionEnd, - getSelectedBlockClientIds, - hasMultiSelection, - hasSelectedBlock, - multiSelect, - selectBlock, - ] - ); - - return { - updateBlockSelection, - }; -} diff --git a/packages/block-editor/src/components/off-canvas-editor/use-inserted-block.js b/packages/block-editor/src/components/off-canvas-editor/use-inserted-block.js deleted file mode 100644 index 0e5a25c980a1c..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/use-inserted-block.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as blockEditorStore } from '../../store'; - -export const useInsertedBlock = ( insertedBlockClientId ) => { - const { insertedBlockAttributes, insertedBlockName } = useSelect( - ( select ) => { - const { getBlockName, getBlockAttributes } = - select( blockEditorStore ); - - return { - insertedBlockAttributes: getBlockAttributes( - insertedBlockClientId - ), - insertedBlockName: getBlockName( insertedBlockClientId ), - }; - }, - [ insertedBlockClientId ] - ); - - const { updateBlockAttributes } = useDispatch( blockEditorStore ); - - const setInsertedBlockAttributes = ( _updatedAttributes ) => { - if ( ! insertedBlockClientId ) return; - updateBlockAttributes( insertedBlockClientId, _updatedAttributes ); - }; - - if ( ! insertedBlockClientId ) { - return { - insertedBlockAttributes: undefined, - insertedBlockName: undefined, - setInsertedBlockAttributes, - }; - } - - return { - insertedBlockAttributes, - insertedBlockName, - setInsertedBlockAttributes, - }; -}; diff --git a/packages/block-editor/src/components/off-canvas-editor/use-list-view-client-ids.js b/packages/block-editor/src/components/off-canvas-editor/use-list-view-client-ids.js deleted file mode 100644 index 5dafa765f16ea..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/use-list-view-client-ids.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * WordPress dependencies - */ - -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as blockEditorStore } from '../../store'; - -export default function useListViewClientIds( blocks ) { - return useSelect( - ( select ) => { - const { - getDraggedBlockClientIds, - getSelectedBlockClientIds, - __unstableGetClientIdsTree, - } = select( blockEditorStore ); - - return { - selectedClientIds: getSelectedBlockClientIds(), - draggedClientIds: getDraggedBlockClientIds(), - clientIdsTree: blocks ? blocks : __unstableGetClientIdsTree(), - }; - }, - [ blocks ] - ); -} diff --git a/packages/block-editor/src/components/off-canvas-editor/use-list-view-drop-zone.js b/packages/block-editor/src/components/off-canvas-editor/use-list-view-drop-zone.js deleted file mode 100644 index 680beafd3c07c..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/use-list-view-drop-zone.js +++ /dev/null @@ -1,260 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect } from '@wordpress/data'; -import { useState, useCallback } from '@wordpress/element'; -import { - useThrottle, - __experimentalUseDropZone as useDropZone, -} from '@wordpress/compose'; - -/** - * Internal dependencies - */ -import { - getDistanceToNearestEdge, - isPointContainedByRect, -} from '../../utils/math'; -import useOnBlockDrop from '../use-on-block-drop'; -import { store as blockEditorStore } from '../../store'; - -/** @typedef {import('../../utils/math').WPPoint} WPPoint */ - -/** - * The type of a drag event. - * - * @typedef {'default'|'file'|'html'} WPDragEventType - */ - -/** - * An array representing data for blocks in the DOM used by drag and drop. - * - * @typedef {Object} WPListViewDropZoneBlocks - * @property {string} clientId The client id for the block. - * @property {string} rootClientId The root client id for the block. - * @property {number} blockIndex The block's index. - * @property {Element} element The DOM element representing the block. - * @property {number} innerBlockCount The number of inner blocks the block has. - * @property {boolean} isDraggedBlock Whether the block is currently being dragged. - * @property {boolean} canInsertDraggedBlocksAsSibling Whether the dragged block can be a sibling of this block. - * @property {boolean} canInsertDraggedBlocksAsChild Whether the dragged block can be a child of this block. - */ - -/** - * An object containing details of a drop target. - * - * @typedef {Object} WPListViewDropZoneTarget - * @property {string} blockIndex The insertion index. - * @property {string} rootClientId The root client id for the block. - * @property {string|undefined} clientId The client id for the block. - * @property {'top'|'bottom'|'inside'} dropPosition The position relative to the block that the user is dropping to. - * 'inside' refers to nesting as an inner block. - */ - -/** - * Determines whether the user positioning the dragged block to nest as an - * inner block. - * - * Presently this is determined by whether the cursor is on the right hand side - * of the block. - * - * @param {WPPoint} point The point representing the cursor position when dragging. - * @param {DOMRect} rect The rectangle. - */ -function isNestingGesture( point, rect ) { - const blockCenterX = rect.left + rect.width / 2; - return point.x > blockCenterX; -} - -// Block navigation is always a vertical list, so only allow dropping -// to the above or below a block. -const ALLOWED_DROP_EDGES = [ 'top', 'bottom' ]; - -/** - * Given blocks data and the cursor position, compute the drop target. - * - * @param {WPListViewDropZoneBlocks} blocksData Data about the blocks in list view. - * @param {WPPoint} position The point representing the cursor position when dragging. - * - * @return {WPListViewDropZoneTarget | undefined} An object containing data about the drop target. - */ -function getListViewDropTarget( blocksData, position ) { - let candidateEdge; - let candidateBlockData; - let candidateDistance; - let candidateRect; - - for ( const blockData of blocksData ) { - if ( blockData.isDraggedBlock ) { - continue; - } - - const rect = blockData.element.getBoundingClientRect(); - const [ distance, edge ] = getDistanceToNearestEdge( - position, - rect, - ALLOWED_DROP_EDGES - ); - - const isCursorWithinBlock = isPointContainedByRect( position, rect ); - if ( - candidateDistance === undefined || - distance < candidateDistance || - isCursorWithinBlock - ) { - candidateDistance = distance; - - const index = blocksData.indexOf( blockData ); - const previousBlockData = blocksData[ index - 1 ]; - - // If dragging near the top of a block and the preceding block - // is at the same level, use the preceding block as the candidate - // instead, as later it makes determining a nesting drop easier. - if ( - edge === 'top' && - previousBlockData && - previousBlockData.rootClientId === blockData.rootClientId && - ! previousBlockData.isDraggedBlock - ) { - candidateBlockData = previousBlockData; - candidateEdge = 'bottom'; - candidateRect = - previousBlockData.element.getBoundingClientRect(); - } else { - candidateBlockData = blockData; - candidateEdge = edge; - candidateRect = rect; - } - - // If the mouse position is within the block, break early - // as the user would intend to drop either before or after - // this block. - // - // This solves an issue where some rows in the list view - // tree overlap slightly due to sub-pixel rendering. - if ( isCursorWithinBlock ) { - break; - } - } - } - - if ( ! candidateBlockData ) { - return; - } - - const isDraggingBelow = candidateEdge === 'bottom'; - - // If the user is dragging towards the bottom of the block check whether - // they might be trying to nest the block as a child. - // If the block already has inner blocks, this should always be treated - // as nesting since the next block in the tree will be the first child. - if ( - isDraggingBelow && - candidateBlockData.canInsertDraggedBlocksAsChild && - ( candidateBlockData.innerBlockCount > 0 || - isNestingGesture( position, candidateRect ) ) - ) { - return { - rootClientId: candidateBlockData.clientId, - blockIndex: 0, - dropPosition: 'inside', - }; - } - - // If dropping as a sibling, but block cannot be inserted in - // this context, return early. - if ( ! candidateBlockData.canInsertDraggedBlocksAsSibling ) { - return; - } - - const offset = isDraggingBelow ? 1 : 0; - return { - rootClientId: candidateBlockData.rootClientId, - clientId: candidateBlockData.clientId, - blockIndex: candidateBlockData.blockIndex + offset, - dropPosition: candidateEdge, - }; -} - -/** - * A react hook for implementing a drop zone in list view. - * - * @return {WPListViewDropZoneTarget} The drop target. - */ -export default function useListViewDropZone() { - const { - getBlockRootClientId, - getBlockIndex, - getBlockCount, - getDraggedBlockClientIds, - canInsertBlocks, - } = useSelect( blockEditorStore ); - const [ target, setTarget ] = useState(); - const { rootClientId: targetRootClientId, blockIndex: targetBlockIndex } = - target || {}; - - const onBlockDrop = useOnBlockDrop( targetRootClientId, targetBlockIndex ); - - const draggedBlockClientIds = getDraggedBlockClientIds(); - const throttled = useThrottle( - useCallback( - ( event, currentTarget ) => { - const position = { x: event.clientX, y: event.clientY }; - const isBlockDrag = !! draggedBlockClientIds?.length; - - const blockElements = Array.from( - currentTarget.querySelectorAll( '[data-block]' ) - ); - - const blocksData = blockElements.map( ( blockElement ) => { - const clientId = blockElement.dataset.block; - const rootClientId = getBlockRootClientId( clientId ); - - return { - clientId, - rootClientId, - blockIndex: getBlockIndex( clientId ), - element: blockElement, - isDraggedBlock: isBlockDrag - ? draggedBlockClientIds.includes( clientId ) - : false, - innerBlockCount: getBlockCount( clientId ), - canInsertDraggedBlocksAsSibling: isBlockDrag - ? canInsertBlocks( - draggedBlockClientIds, - rootClientId - ) - : true, - canInsertDraggedBlocksAsChild: isBlockDrag - ? canInsertBlocks( draggedBlockClientIds, clientId ) - : true, - }; - } ); - - const newTarget = getListViewDropTarget( blocksData, position ); - - if ( newTarget ) { - setTarget( newTarget ); - } - }, - [ draggedBlockClientIds ] - ), - 200 - ); - - const ref = useDropZone( { - onDrop: onBlockDrop, - onDragOver( event ) { - // `currentTarget` is only available while the event is being - // handled, so get it now and pass it to the thottled function. - // https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget - throttled( event, event.currentTarget ); - }, - onDragEnd() { - throttled.cancel(); - setTarget( null ); - }, - } ); - - return { ref, target }; -} diff --git a/packages/block-editor/src/components/off-canvas-editor/use-list-view-expand-selected-item.js b/packages/block-editor/src/components/off-canvas-editor/use-list-view-expand-selected-item.js deleted file mode 100644 index 09b5e09e4713a..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/use-list-view-expand-selected-item.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * WordPress dependencies - */ -import { useEffect, useState } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { store as blockEditorStore } from '../../store'; - -export default function useListViewExpandSelectedItem( { - firstSelectedBlockClientId, - setExpandedState, -} ) { - const [ selectedTreeId, setSelectedTreeId ] = useState( null ); - const { selectedBlockParentClientIds } = useSelect( - ( select ) => { - const { getBlockParents } = select( blockEditorStore ); - return { - selectedBlockParentClientIds: getBlockParents( - firstSelectedBlockClientId, - false - ), - }; - }, - [ firstSelectedBlockClientId ] - ); - - const parentClientIds = - Array.isArray( selectedBlockParentClientIds ) && - selectedBlockParentClientIds.length - ? selectedBlockParentClientIds - : null; - - // Expand tree when a block is selected. - useEffect( () => { - // If the selectedTreeId is the same as the selected block, - // it means that the block was selected using the block list tree. - if ( selectedTreeId === firstSelectedBlockClientId ) { - return; - } - - // If the selected block has parents, get the top-level parent. - if ( parentClientIds ) { - // If the selected block has parents, - // expand the tree branch. - setExpandedState( { - type: 'expand', - clientIds: selectedBlockParentClientIds, - } ); - } - }, [ firstSelectedBlockClientId ] ); - - return { - setSelectedTreeId, - }; -} diff --git a/packages/block-editor/src/components/off-canvas-editor/utils.js b/packages/block-editor/src/components/off-canvas-editor/utils.js deleted file mode 100644 index f53f5a4cd4884..0000000000000 --- a/packages/block-editor/src/components/off-canvas-editor/utils.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * WordPress dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; - -export const getBlockPositionDescription = ( position, siblingCount, level ) => - sprintf( - /* translators: 1: The numerical position of the block. 2: The total number of blocks. 3. The level of nesting for the block. */ - __( 'Block %1$d of %2$d, Level %3$d' ), - position, - siblingCount, - level - ); - -/** - * Returns true if the client ID occurs within the block selection or multi-selection, - * or false otherwise. - * - * @param {string} clientId Block client ID. - * @param {string|string[]} selectedBlockClientIds Selected block client ID, or an array of multi-selected blocks client IDs. - * - * @return {boolean} Whether the block is in multi-selection set. - */ -export const isClientIdSelected = ( clientId, selectedBlockClientIds ) => - Array.isArray( selectedBlockClientIds ) && selectedBlockClientIds.length - ? selectedBlockClientIds.indexOf( clientId ) !== -1 - : selectedBlockClientIds === clientId; - -/** - * From a start and end clientId of potentially different nesting levels, - * return the nearest-depth ids that have a common level of depth in the - * nesting hierarchy. For multiple block selection, this ensure that the - * selection is always at the same nesting level, and not split across - * separate levels. - * - * @param {string} startId The first id of a selection. - * @param {string} endId The end id of a selection, usually one that has been clicked on. - * @param {string[]} startParents An array of ancestor ids for the start id, in descending order. - * @param {string[]} endParents An array of ancestor ids for the end id, in descending order. - * @return {Object} An object containing the start and end ids. - */ -export function getCommonDepthClientIds( - startId, - endId, - startParents, - endParents -) { - const startPath = [ ...startParents, startId ]; - const endPath = [ ...endParents, endId ]; - const depth = Math.min( startPath.length, endPath.length ) - 1; - const start = startPath[ depth ]; - const end = endPath[ depth ]; - - return { - start, - end, - }; -} diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index a7357311eedbe..5a451a4a58a2a 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -4,7 +4,6 @@ import * as globalStyles from './components/global-styles'; import { ExperimentalBlockEditorProvider } from './components/provider'; import { lock } from './lock-unlock'; -import OffCanvasEditor from './components/off-canvas-editor'; import ResizableBoxPopover from './components/resizable-box-popover'; import { ComposedPrivateInserter as PrivateInserter } from './components/inserter'; import { PrivateListView } from './components/list-view'; @@ -19,7 +18,6 @@ export const privateApis = {}; lock( privateApis, { ...globalStyles, ExperimentalBlockEditorProvider, - OffCanvasEditor, PrivateInserter, PrivateListView, ResizableBoxPopover, diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index d5ec18cb4c69f..5eafc0766ae22 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -59,7 +59,4 @@ @import "./components/preview-options/style.scss"; @import "./components/spacing-sizes-control/style.scss"; -// Experiments. -@import "./components/off-canvas-editor/style.scss"; - @include wordpress-admin-schemes(); From bf80b72b08932900b38e85e060cdf0f5ae96ecf7 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 19 May 2023 13:29:55 +1000 Subject: [PATCH 16/26] $revisions_controller is not used. Let's delete it. (#50763) --- .../class-gutenberg-rest-global-styles-controller-6-3.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php b/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php index 5eeb0a1014aed..09b0f4ec65dbf 100644 --- a/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php +++ b/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php @@ -10,14 +10,6 @@ * Base Global Styles REST API Controller. */ class Gutenberg_REST_Global_Styles_Controller_6_3 extends Gutenberg_REST_Global_Styles_Controller_6_2 { - /** - * Revision controller. - * - * @since 6.3.0 - * @var WP_REST_Revisions_Controller - */ - private $revisions_controller; - /** * Prepares links for the request. * From a8da93f061224a93ca6ea0c6ab311021e6dba7ba Mon Sep 17 00:00:00 2001 From: Michael Burridge Date: Fri, 19 May 2023 07:18:04 +0100 Subject: [PATCH 17/26] Minor updates to theme.json schema pages (#50742) * Minor updates to theme.json schema pages --- docs/manifest.json | 4 ++-- .../theme-json-reference/theme-json-living.md | 23 ++++++++++++++----- .../theme-json-reference/theme-json-v1.md | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/docs/manifest.json b/docs/manifest.json index 8b6677d39c559..d6759f051a679 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -594,13 +594,13 @@ "parent": "reference-guides" }, { - "title": "Version 2 (living reference)", + "title": "Theme.json Version 2", "slug": "theme-json-living", "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-living.md", "parent": "theme-json-reference" }, { - "title": "Version 1 Reference", + "title": "Theme.json Version 1 Reference", "slug": "theme-json-v1", "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-v1.md", "parent": "theme-json-reference" diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 505596819bfb4..3299739575430 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -1,16 +1,27 @@ -# Version 2 (living reference) +# Theme.json Version 2 -> This is the living specification for the **version 2** of theme.json. This version works with WordPress 5.9 or later, and the latest Gutenberg plugin. +> This is the living specification for **version 2** of `theme.json`. This version works with WordPress 5.9 or later, and the latest Gutenberg plugin. > -> There're related documents you may be interested in: the [theme.json v1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) specification and the [reference to migrate from theme.json v1 to v2](/docs/reference-guides/theme-json-reference/theme-json-migrations.md). +> There are some related documents that you may be interested in: +> - the [theme.json v1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) specification, and +> - the [reference to migrate from theme.json v1 to v2](/docs/reference-guides/theme-json-reference/theme-json-migrations.md). -This reference guide lists the settings and style properties defined in the theme.json schema. See the [theme.json how to guide](/docs/how-to-guides/themes/theme-json.md) for examples and guide on how to use the theme.json file in your theme. +This reference guide lists the settings and style properties defined in the `theme.json` schema. See the [theme.json how to guide](/docs/how-to-guides/themes/theme-json.md) for examples and guidance on how to use the `theme.json` file in your theme. ## Schema -It can be difficult to remember the theme.json settings and properties while you develop, so a JSON scheme was created to help. The schema is available at https://schemas.wp.org/trunk/theme.json +Remembering the `theme.json` settings and properties while you develop can be difficult, so a [JSON schema](https://schemas.wp.org/trunk/theme.json) was created to help. + +Code editors can pick up the schema and can provide helpful hints and suggestions such as tooltips, autocomplete, or schema validation in the editor. To use the schema in Visual Studio Code, add `$schema`: "https://schemas.wp.org/trunk/theme.json" to the beginning of your theme.json file together with a `version` corresponding to the version you wish to use, e.g.: + +``` +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 2, + ... +} +``` -Code editors can pick up the schema and can provide help like tooltips, autocomplete, or schema validation in the editor. To use the schema in Visual Studio Code, add "$schema": "https://schemas.wp.org/trunk/theme.json" to the beginning of your theme.json file. ## Settings diff --git a/docs/reference-guides/theme-json-reference/theme-json-v1.md b/docs/reference-guides/theme-json-reference/theme-json-v1.md index d0ed8a70a3110..3e7096ee420ef 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-v1.md +++ b/docs/reference-guides/theme-json-reference/theme-json-v1.md @@ -1,4 +1,4 @@ -# Version 1 Reference +# Theme.json Version 1 Reference Theme.json version 2 has been released, see the [theme.json migration guide](/docs/reference-guides/theme-json-reference/theme-json-migrations.md#migrating-from-v1-to-v2) for updating to the latest version. From ac28fa6dcd868bd9d3a0918ffd9105f0febf3828 Mon Sep 17 00:00:00 2001 From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com> Date: Fri, 19 May 2023 10:18:12 +0200 Subject: [PATCH 18/26] Support negation operator in selectors in the Interactivity API (#50732) * Support negation operator in selectors * Change file block to use negation operator --- lib/experimental/interactivity-api/blocks.php | 2 +- .../block-library/src/file/interactivity.js | 6 ++--- .../src/utils/interactivity/hooks.js | 24 ++++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/experimental/interactivity-api/blocks.php b/lib/experimental/interactivity-api/blocks.php index 755c1d1d4fa7d..647bad93c0d03 100644 --- a/lib/experimental/interactivity-api/blocks.php +++ b/lib/experimental/interactivity-api/blocks.php @@ -23,7 +23,7 @@ function gutenberg_block_core_file_add_directives_to_content( $block_content, $b $processor->next_tag(); $processor->set_attribute( 'data-wp-island', '' ); $processor->next_tag( 'object' ); - $processor->set_attribute( 'data-wp-bind.hidden', 'selectors.core.file.hasNoPdfPreview' ); + $processor->set_attribute( 'data-wp-bind.hidden', '!selectors.core.file.hasPdfPreview' ); $processor->set_attribute( 'hidden', true ); return $processor->get_updated_html(); } diff --git a/packages/block-library/src/file/interactivity.js b/packages/block-library/src/file/interactivity.js index cf9ae41002b27..8060f7addf3a2 100644 --- a/packages/block-library/src/file/interactivity.js +++ b/packages/block-library/src/file/interactivity.js @@ -2,15 +2,13 @@ * Internal dependencies */ import { store } from '../utils/interactivity'; -import { browserSupportsPdfs } from './utils'; +import { browserSupportsPdfs as hasPdfPreview } from './utils'; store( { selectors: { core: { file: { - hasNoPdfPreview() { - return ! browserSupportsPdfs(); - }, + hasPdfPreview, }, }, }, diff --git a/packages/block-library/src/utils/interactivity/hooks.js b/packages/block-library/src/utils/interactivity/hooks.js index ca3bd20964d51..072c01641a59c 100644 --- a/packages/block-library/src/utils/interactivity/hooks.js +++ b/packages/block-library/src/utils/interactivity/hooks.js @@ -19,26 +19,28 @@ export const directive = ( name, cb ) => { // Resolve the path to some property of the store object. const resolve = ( path, ctx ) => { - // If path starts with !, remove it and save a flag. - const hasNegationOperator = - path[ 0 ] === '!' && !! ( path = path.slice( 1 ) ); let current = { ...store, context: ctx }; path.split( '.' ).forEach( ( p ) => ( current = current[ p ] ) ); - return hasNegationOperator ? ! current : current; + return current; }; // Generate the evaluate function. const getEvaluate = ( { ref } = {} ) => ( path, extraArgs = {} ) => { + // If path starts with !, remove it and save a flag. + const hasNegationOperator = + path[ 0 ] === '!' && !! ( path = path.slice( 1 ) ); const value = resolve( path, extraArgs.context ); - return typeof value === 'function' - ? value( { - ref: ref.current, - ...store, - ...extraArgs, - } ) - : value; + const returnValue = + typeof value === 'function' + ? value( { + ref: ref.current, + ...store, + ...extraArgs, + } ) + : value; + return hasNegationOperator ? ! returnValue : returnValue; }; // Directive wrapper. From f91be8d47fed17f4968c7b7a3dbb1dd36bca8917 Mon Sep 17 00:00:00 2001 From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com> Date: Fri, 19 May 2023 11:16:57 +0200 Subject: [PATCH 19/26] Polish experimental navigation block (#50670) * Remove unnecessary `async` in `effects` * Fix page list block and multiple submenus * Use `requestUtils.createPage` in Page list tests * Rename effect to `initMenu` * Fix typo --- lib/experimental/interactivity-api/blocks.php | 25 ++----- .../src/navigation/interactivity.js | 4 +- test/e2e/artifacts/storage-states/admin.json | 67 ++++++++++++++++++- 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/lib/experimental/interactivity-api/blocks.php b/lib/experimental/interactivity-api/blocks.php index 647bad93c0d03..1c6e7d2b1fbb9 100644 --- a/lib/experimental/interactivity-api/blocks.php +++ b/lib/experimental/interactivity-api/blocks.php @@ -47,8 +47,8 @@ function gutenberg_block_core_file_add_directives_to_content( $block_content, $b * data-wp-class.has-modal-open="context.core.navigation.isMenuOpen" * data-wp-class.is-menu-open="context.core.navigation.isMenuOpen" * data-wp-bind.aria-hidden="!context.core.navigation.isMenuOpen" - * data-wp-effect="effects.core.navigation.initModal" - * data-wp-on.keydow="actions.core.navigation.handleMenuKeydown" + * data-wp-effect="effects.core.navigation.initMenu" + * data-wp-on.keydown="actions.core.navigation.handleMenuKeydown" * data-wp-on.focusout="actions.core.navigation.handleMenuFocusout" * tabindex="-1" * > @@ -97,21 +97,6 @@ function gutenberg_block_core_navigation_add_directives_to_markup( $block_conten // If the open modal button not found, we handle submenus immediately. $w = new WP_HTML_Tag_Processor( $w->get_updated_html() ); - // Add directives to the menu container. - if ( $w->next_tag( - array( - 'tag_name' => 'UL', - 'class_name' => 'wp-block-navigation__container', - ) - ) ) { - $w->set_attribute( 'data-wp-class.is-menu-open', 'context.core.navigation.isMenuOpen' ); - $w->set_attribute( 'data-wp-bind.aria-hidden', '!context.core.navigation.isMenuOpen' ); - $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initModal' ); - $w->set_attribute( 'data-wp-on.keydown', 'actions.core.navigation.handleMenuKeydown' ); - $w->set_attribute( 'data-wp-on.focusout', 'actions.core.navigation.handleMenuFocusout' ); - $w->set_attribute( 'tabindex', '-1' ); - }; - gutenberg_block_core_navigation_add_directives_to_submenu( $w ); return $w->get_updated_html(); @@ -127,7 +112,7 @@ function gutenberg_block_core_navigation_add_directives_to_markup( $block_conten $w->set_attribute( 'data-wp-class.has-modal-open', 'context.core.navigation.isMenuOpen' ); $w->set_attribute( 'data-wp-class.is-menu-open', 'context.core.navigation.isMenuOpen' ); $w->set_attribute( 'data-wp-bind.aria-hidden', '!context.core.navigation.isMenuOpen' ); - $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initModal' ); + $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' ); $w->set_attribute( 'data-wp-on.keydown', 'actions.core.navigation.handleMenuKeydown' ); $w->set_attribute( 'data-wp-on.focusout', 'actions.core.navigation.handleMenuFocusout' ); $w->set_attribute( 'tabindex', '-1' ); @@ -191,7 +176,7 @@ function gutenberg_block_core_navigation_add_directives_to_markup( $block_conten * Title *
      @@ -233,7 +218,7 @@ function gutenberg_block_core_navigation_add_directives_to_submenu( $w ) { 'class_name' => 'wp-block-navigation__submenu-container', ) ) ) { - $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initModal' ); + $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' ); $w->set_attribute( 'data-wp-on.focusout', 'actions.core.navigation.handleMenuFocusout' ); $w->set_attribute( 'data-wp-on.keydown', 'actions.core.navigation.handleMenuKeydown' ); }; diff --git a/packages/block-library/src/navigation/interactivity.js b/packages/block-library/src/navigation/interactivity.js index bf537ceb803a4..b147d651d032b 100644 --- a/packages/block-library/src/navigation/interactivity.js +++ b/packages/block-library/src/navigation/interactivity.js @@ -21,7 +21,7 @@ store( { effects: { core: { navigation: { - initModal: async ( { context, ref } ) => { + initMenu: ( { context, ref } ) => { if ( context.core.navigation.isMenuOpen ) { const focusableElements = ref.querySelectorAll( focusableSelectors ); @@ -32,7 +32,7 @@ store( { focusableElements[ focusableElements.length - 1 ]; } }, - focusFirstElement: async ( { context, ref } ) => { + focusFirstElement: ( { context, ref } ) => { if ( context.core.navigation.isMenuOpen ) { ref.querySelector( '.wp-block-navigation-item > *:first-child' diff --git a/test/e2e/artifacts/storage-states/admin.json b/test/e2e/artifacts/storage-states/admin.json index cd90306028e8a..cae09dbcd0eac 100644 --- a/test/e2e/artifacts/storage-states/admin.json +++ b/test/e2e/artifacts/storage-states/admin.json @@ -1 +1,66 @@ -{"cookies":[{"name":"wordpress_test_cookie","value":"WP%20Cookie%20check","domain":"localhost","path":"/","expires":-1,"httpOnly":false,"secure":false,"sameSite":"Lax"},{"name":"wordpress_23778236db82f19306f247e20a353a99","value":"admin%7C1684446357%7Cdx01QADx42SHH9s4ikPkhkS07FzxnWES5FY2SsnwL7v%7C45404d74460259bc9148f2357f2180af488d65921b10ed5981fff860afa5c8ca","domain":"localhost","path":"/wp-content/plugins","expires":-1,"httpOnly":true,"secure":false,"sameSite":"Lax"},{"name":"wordpress_23778236db82f19306f247e20a353a99","value":"admin%7C1684446357%7Cdx01QADx42SHH9s4ikPkhkS07FzxnWES5FY2SsnwL7v%7C45404d74460259bc9148f2357f2180af488d65921b10ed5981fff860afa5c8ca","domain":"localhost","path":"/wp-admin","expires":-1,"httpOnly":true,"secure":false,"sameSite":"Lax"},{"name":"wordpress_logged_in_23778236db82f19306f247e20a353a99","value":"admin%7C1684446357%7Cdx01QADx42SHH9s4ikPkhkS07FzxnWES5FY2SsnwL7v%7C8ace4a8f867bc4e587d5264662296f90fcd133710cd0dd3386a92801816bd5d1","domain":"localhost","path":"/","expires":-1,"httpOnly":true,"secure":false,"sameSite":"Lax"},{"name":"wp-settings-1","value":"editor%3Dtinymce","domain":"localhost","path":"/","expires":1715809558.14,"httpOnly":false,"secure":false,"sameSite":"Lax"},{"name":"wp-settings-time-1","value":"1684273558","domain":"localhost","path":"/","expires":1715809558.14,"httpOnly":false,"secure":false,"sameSite":"Lax"}],"nonce":"fe590a7aae","rootURL":"http://localhost:8889/index.php?rest_route=/"} \ No newline at end of file +{ + "cookies": [ + { + "name": "wordpress_test_cookie", + "value": "WP%20Cookie%20check", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": false, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "wordpress_23778236db82f19306f247e20a353a99", + "value": "admin%7C1684446357%7Cdx01QADx42SHH9s4ikPkhkS07FzxnWES5FY2SsnwL7v%7C45404d74460259bc9148f2357f2180af488d65921b10ed5981fff860afa5c8ca", + "domain": "localhost", + "path": "/wp-content/plugins", + "expires": -1, + "httpOnly": true, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "wordpress_23778236db82f19306f247e20a353a99", + "value": "admin%7C1684446357%7Cdx01QADx42SHH9s4ikPkhkS07FzxnWES5FY2SsnwL7v%7C45404d74460259bc9148f2357f2180af488d65921b10ed5981fff860afa5c8ca", + "domain": "localhost", + "path": "/wp-admin", + "expires": -1, + "httpOnly": true, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "wordpress_logged_in_23778236db82f19306f247e20a353a99", + "value": "admin%7C1684446357%7Cdx01QADx42SHH9s4ikPkhkS07FzxnWES5FY2SsnwL7v%7C8ace4a8f867bc4e587d5264662296f90fcd133710cd0dd3386a92801816bd5d1", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": true, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "wp-settings-1", + "value": "editor%3Dtinymce", + "domain": "localhost", + "path": "/", + "expires": 1715809558.14, + "httpOnly": false, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "wp-settings-time-1", + "value": "1684273558", + "domain": "localhost", + "path": "/", + "expires": 1715809558.14, + "httpOnly": false, + "secure": false, + "sameSite": "Lax" + } + ], + "nonce": "fe590a7aae", + "rootURL": "http://localhost:8889/index.php?rest_route=/" +} From c1238e4700fe948291e17d1c8cbddd69ab25bfaa Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Fri, 19 May 2023 21:25:48 +1200 Subject: [PATCH 20/26] Force display of in custom css input boxes to LTR (#50768) --- packages/block-editor/src/components/global-styles/style.scss | 3 +++ packages/edit-site/src/components/global-styles/style.scss | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/block-editor/src/components/global-styles/style.scss b/packages/block-editor/src/components/global-styles/style.scss index 31d22afec3776..46429ea5c4776 100644 --- a/packages/block-editor/src/components/global-styles/style.scss +++ b/packages/block-editor/src/components/global-styles/style.scss @@ -44,6 +44,9 @@ .block-editor-global-styles-advanced-panel__custom-css-input textarea { font-family: $editor_html_font; + // CSS input is always LTR regardless of language. + /*rtl:ignore*/ + direction: ltr; } .block-editor-global-styles-advanced-panel__custom-css-validation-wrapper { diff --git a/packages/edit-site/src/components/global-styles/style.scss b/packages/edit-site/src/components/global-styles/style.scss index 87f0cfdac44d1..8bc5efab2a4ac 100644 --- a/packages/edit-site/src/components/global-styles/style.scss +++ b/packages/edit-site/src/components/global-styles/style.scss @@ -143,6 +143,9 @@ .components-textarea-control__input { flex: 1 1 auto; + // CSS input is always LTR regardless of language. + /*rtl:ignore*/ + direction: ltr; } } } From fc3c4f506ecf3977e653dd06fc003f16000c74cd Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 19 May 2023 12:45:38 +0200 Subject: [PATCH 21/26] Add new experimental version of DropdownMenu (#49473) * Add Radix deps * Re-export radix dropdown with some custom styles * Add sample story * Start aggregating some components * Improve storybook config * Use better colors * Fix styles * Move types to external file * Namespace all styled radix components under "DropdownMenuStyled" namespace * Use dot icon for radio items instead of check icon * Pass portal props to portal * Add DropdownSubMenu component * Improve stories to use high-level components * Update notes * Comment out unused import * Use menu groups * draft section in contributing guidelines * Use wordpress icons instead of `@radix-ui/react-icons` * Add custom `icon` prop to `DropdownMenuItem` * Add missing alignment property for sub trigger * Extract animation variables * Align colors more closely to WordPress styles * Add border to dropdown container * Increase spacing, use `space` util * Use font utils * Add focus-visible outline * Alternative styling for submenu triggers * Pick only selected props for DropdownMenu * Rewrite Storybook example to support controls * DropdownMenuItem: Pick only selected props, support prefix/suffix * Remove forwarded refs * Playing around with asChild and ref forwarding * Updated remaining props * Add suffix to checkbox / radio items * Tweak styles (remove arrow, box shadow, center Storybook example) * Try separate submenu trigger component * Move layout wrapper to decorator * Do not use `asChild` for indicator wrappers * Remove solved TODOs * RTL support * Add support for reduced motion * Expose component via lock APIs * Polish styles * Generalize storybook * Focus/hover styles * Refine styles * Tweak storybook example (move separators out of groups, better item text) * Add unit tests * Move legacy implementation to v1 subfolder, move new implementation to v2 folder * READMEs * Tidy up types and JSDocs * CHANGELOG * Update Storybook example title * Fix imports * Fix storybook path * Fix test import path * Fix manifest.json * Move legacy files back to where they were, v2 to separate dropdown-menu-v2 folder, fix docs * Update CHANGELOG and fix formatting * Use WP Button instead of custom button * Remove unnecessary decorator with shared context, using private state instead * Remove opinionated wrapper centering styles * Update READMEs: remove v1, add experimental callout * Fix OG DropdownMenu import syntax to be consistent with TypeScript imports across the package --------- Co-authored-by: Lena Morita --- docs/tool/manifest.js | 1 + package-lock.json | 333 +++++++ packages/components/CHANGELOG.md | 1 + packages/components/CONTRIBUTING.md | 10 + packages/components/package.json | 3 +- .../components/src/dropdown-menu-v2/README.md | 392 +++++++++ .../components/src/dropdown-menu-v2/index.tsx | 241 ++++++ .../src/dropdown-menu-v2/stories/index.tsx | 193 +++++ .../components/src/dropdown-menu-v2/styles.ts | 263 ++++++ .../src/dropdown-menu-v2/test/index.tsx | 816 ++++++++++++++++++ .../components/src/dropdown-menu-v2/types.ts | 250 ++++++ .../src/dropdown-menu/stories/index.tsx | 3 +- .../src/dropdown-menu/test/index.tsx | 2 +- packages/components/src/private-apis.ts | 22 + 14 files changed, 2527 insertions(+), 3 deletions(-) create mode 100644 packages/components/src/dropdown-menu-v2/README.md create mode 100644 packages/components/src/dropdown-menu-v2/index.tsx create mode 100644 packages/components/src/dropdown-menu-v2/stories/index.tsx create mode 100644 packages/components/src/dropdown-menu-v2/styles.ts create mode 100644 packages/components/src/dropdown-menu-v2/test/index.tsx create mode 100644 packages/components/src/dropdown-menu-v2/types.ts diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index e407c47e7284b..d3ed5d61dc0bb 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -14,6 +14,7 @@ const componentPaths = glob( 'packages/components/src/*/**/README.md', { '**/src/ui/**/README.md', 'packages/components/src/theme/README.md', 'packages/components/src/view/README.md', + 'packages/components/src/dropdown-menu-v2/README.md', ], } ); const packagePaths = glob( 'packages/*/package.json' ) diff --git a/package-lock.json b/package-lock.json index 040fe3911f377..041a64c8600e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7125,6 +7125,67 @@ "@babel/runtime": "^7.13.10" } }, + "@radix-ui/react-arrow": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.2.tgz", + "integrity": "sha512-fqYwhhI9IarZ0ll2cUSfKuXHlJK0qE4AfnRrPBbRwEH/4mGQn04/QFGomLi8TXWIdv9WJk//KgGm+aDxVIr1wA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.2" + }, + "dependencies": { + "@radix-ui/react-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz", + "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.1" + } + }, + "@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + } + } + } + }, + "@radix-ui/react-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.2.tgz", + "integrity": "sha512-s8WdQQ6wNXpaxdZ308KSr8fEWGrg4un8i4r/w7fhiS4ElRNjk5rRcl0/C6TANG2LvLOGIxtzo/jAg6Qf73TEBw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-slot": "1.0.1" + }, + "dependencies": { + "@radix-ui/react-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz", + "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.1" + } + }, + "@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + } + } + } + }, "@radix-ui/react-compose-refs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", @@ -7163,6 +7224,14 @@ "react-remove-scroll": "2.5.4" } }, + "@radix-ui/react-direction": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz", + "integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, "@radix-ui/react-dismissable-layer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz", @@ -7176,6 +7245,41 @@ "@radix-ui/react-use-escape-keydown": "1.0.0" } }, + "@radix-ui/react-dropdown-menu": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.4.tgz", + "integrity": "sha512-y6AT9+MydyXcByivdK1+QpjWoKaC7MLjkS/cH1Q3keEyMvDkiY85m8o2Bi6+Z1PPUlCsMULopxagQOSfN0wahg==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-id": "1.0.0", + "@radix-ui/react-menu": "2.0.4", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.0" + }, + "dependencies": { + "@radix-ui/react-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz", + "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.1" + } + }, + "@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + } + } + } + }, "@radix-ui/react-focus-guards": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz", @@ -7204,6 +7308,166 @@ "@radix-ui/react-use-layout-effect": "1.0.0" } }, + "@radix-ui/react-menu": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.4.tgz", + "integrity": "sha512-mzKR47tZ1t193trEqlQoJvzY4u9vYfVH16ryBrVrCAGZzkgyWnMQYEZdUkM7y8ak9mrkKtJiqB47TlEnubeOFQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-collection": "1.0.2", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-direction": "1.0.0", + "@radix-ui/react-dismissable-layer": "1.0.3", + "@radix-ui/react-focus-guards": "1.0.0", + "@radix-ui/react-focus-scope": "1.0.2", + "@radix-ui/react-id": "1.0.0", + "@radix-ui/react-popper": "1.1.1", + "@radix-ui/react-portal": "1.0.2", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-roving-focus": "1.0.3", + "@radix-ui/react-slot": "1.0.1", + "@radix-ui/react-use-callback-ref": "1.0.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "dependencies": { + "@radix-ui/react-dismissable-layer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.3.tgz", + "integrity": "sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-escape-keydown": "1.0.2" + } + }, + "@radix-ui/react-focus-scope": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.2.tgz", + "integrity": "sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.0" + } + }, + "@radix-ui/react-portal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.2.tgz", + "integrity": "sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.2" + } + }, + "@radix-ui/react-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz", + "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.1" + } + }, + "@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + } + }, + "@radix-ui/react-use-escape-keydown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.2.tgz", + "integrity": "sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.0" + } + }, + "react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "requires": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + } + } + } + }, + "@radix-ui/react-popper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.1.tgz", + "integrity": "sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==", + "requires": { + "@babel/runtime": "^7.13.10", + "@floating-ui/react-dom": "0.7.2", + "@radix-ui/react-arrow": "1.0.2", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0", + "@radix-ui/react-use-rect": "1.0.0", + "@radix-ui/react-use-size": "1.0.0", + "@radix-ui/rect": "1.0.0" + }, + "dependencies": { + "@floating-ui/core": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz", + "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==" + }, + "@floating-ui/dom": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz", + "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==", + "requires": { + "@floating-ui/core": "^0.7.3" + } + }, + "@floating-ui/react-dom": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz", + "integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==", + "requires": { + "@floating-ui/dom": "^0.5.3", + "use-isomorphic-layout-effect": "^1.1.1" + } + }, + "@radix-ui/react-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz", + "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.1" + } + }, + "@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + } + } + } + }, "@radix-ui/react-portal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz", @@ -7232,6 +7496,43 @@ "@radix-ui/react-slot": "1.0.0" } }, + "@radix-ui/react-roving-focus": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.3.tgz", + "integrity": "sha512-stjCkIoMe6h+1fWtXlA6cRfikdBzCLp3SnVk7c48cv/uy3DTGoXhN76YaOYUJuy3aEDvDIKwKR5KSmvrtPvQPQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-collection": "1.0.2", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-direction": "1.0.0", + "@radix-ui/react-id": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-controllable-state": "1.0.0" + }, + "dependencies": { + "@radix-ui/react-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz", + "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.1" + } + }, + "@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + } + } + } + }, "@radix-ui/react-slot": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz", @@ -7275,6 +7576,32 @@ "@babel/runtime": "^7.13.10" } }, + "@radix-ui/react-use-rect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz", + "integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/rect": "1.0.0" + } + }, + "@radix-ui/react-use-size": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz", + "integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.0" + } + }, + "@radix-ui/rect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz", + "integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, "@react-native-clipboard/clipboard": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.9.0.tgz", @@ -17003,6 +17330,7 @@ "@emotion/styled": "^11.6.0", "@emotion/utils": "^1.0.0", "@floating-ui/react-dom": "1.0.0", + "@radix-ui/react-dropdown-menu": "^2.0.4", "@use-gesture/react": "^10.2.24", "@wordpress/a11y": "file:packages/a11y", "@wordpress/compose": "file:packages/compose", @@ -56292,6 +56620,11 @@ "tslib": "^2.0.0" } }, + "use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==" + }, "use-lilius": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/use-lilius/-/use-lilius-2.0.1.tgz", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 6c00d6e22027b..257d0545f4223 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -10,6 +10,7 @@ - `Modal`: Remove children container's unused class name ([#50655](https://github.com/WordPress/gutenberg/pull/50655)). - `DropdownMenu`: Convert to TypeScript ([#50187](https://github.com/WordPress/gutenberg/pull/50187)). +- Added experimental v2 of `DropdownMenu` ([#49473](https://github.com/WordPress/gutenberg/pull/49473)). ## 24.0.0 (2023-05-10) diff --git a/packages/components/CONTRIBUTING.md b/packages/components/CONTRIBUTING.md index 8464a4a732744..bf7569a19ddba 100644 --- a/packages/components/CONTRIBUTING.md +++ b/packages/components/CONTRIBUTING.md @@ -20,6 +20,7 @@ For an example of a component that follows these requirements, take a look at [` - [README example](#README-example) - [Folder structure](#folder-structure) - [TypeScript migration guide](#refactoring-a-component-to-typescript) +- [Using Radix UI primitives](#using-radix-ui-primitives) ## Introducing new components @@ -639,3 +640,12 @@ Given a component folder (e.g. `packages/components/src/unit-control`): 11. Convert unit tests. 1. Rename test file extensions from `.js` to `.tsx`. 2. Fix all TypeScript errors. + +## Using Radix UI primitives + +Useful links: + +- [online docs](https://www.radix-ui.com/docs/primitives/overview/introduction) +- [repo](https://github.com/radix-ui/primitives) — useful for: + - inspecting source code + - running storybook examples (`yarn install && yarn dev`) diff --git a/packages/components/package.json b/packages/components/package.json index 0077e73d3f1da..0339100948a01 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -38,6 +38,7 @@ "@emotion/styled": "^11.6.0", "@emotion/utils": "^1.0.0", "@floating-ui/react-dom": "1.0.0", + "@radix-ui/react-dropdown-menu": "^2.0.4", "@use-gesture/react": "^10.2.24", "@wordpress/a11y": "file:../a11y", "@wordpress/compose": "file:../compose", @@ -85,4 +86,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/packages/components/src/dropdown-menu-v2/README.md b/packages/components/src/dropdown-menu-v2/README.md new file mode 100644 index 0000000000000..c3896f8ca1109 --- /dev/null +++ b/packages/components/src/dropdown-menu-v2/README.md @@ -0,0 +1,392 @@ +# `DropdownMenu` (v2) + +
      +This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes. +
      + +`DropdownMenu` displays a menu to the user (such as a set of actions or functions) triggered by a button. + + +## Design guidelines + +### Usage + +#### When to use a DropdownMenu + +Use a DropdownMenu when you want users to: + +- Choose an action or change a setting from a list, AND +- Only see the available choices contextually. + +`DropdownMenu` is a React component to render an expandable menu of buttons. It is similar in purpose to a `