From c4b5770dc039afae01dedba670ce165a27a302b1 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Mon, 14 Nov 2022 06:45:25 -0700 Subject: [PATCH 001/380] Navigation Link: Add the URL field to the Navigation Link inspector controls (#45751) --- packages/block-library/src/navigation-link/edit.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 1b1b530dcdcdf..ccd0aae57bcbb 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -716,6 +716,14 @@ export default function NavigationLinkEdit( { + { + setAttributes( { url: urlValue } ); + } } + label={ __( 'URL' ) } + autoComplete="off" + /> { From db22cc77acefd365f7b4fb7e05d418823b1541d8 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Tue, 15 Nov 2022 00:22:48 +0900 Subject: [PATCH 002/380] Patterns: Add padding to the pattern explorer list (#45730) --- packages/block-editor/src/components/inserter/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 5aa9b688f2502..785b9a414a5d7 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -442,7 +442,8 @@ $block-inserter-tabs-height: 44px; } &__list { - margin-left: $sidebar-width - $grid-unit-40; + margin-left: $sidebar-width; + padding: $grid-unit-30 0 $grid-unit-40; } .block-editor-block-patterns-list { From 36b6a1996bfbac563167cd319aa454ebb9e316b2 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Mon, 14 Nov 2022 17:54:03 +0200 Subject: [PATCH 003/380] Converts paragraphs to headings with keyboard shortcuts (#44681) * adds shortcuts for heading transforms to the paragraph block * Implements feedback on hook location, UX and usage. - Moves the functionality from the paragraph block to the block editor package - Implements the shortcuts for paragraph and heading allowing cycling between them - Refactors the code in a less repetitive shape - Adds access+0 as a way to convert headings to paragraphs - Implements cycling through heading levels via access+[1-6] - Registers shortcuts only once - Adds a new prop to RichText allowing for onKeyDown custom handlers - Removes the keyboard-shortcuts dependency from block libary Co-authored-by: Daniel Richards <677833+talldan@users.noreply.github.com> Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Co-authored-by: Dave Smith <444434+getdave@users.noreply.github.com> * Refactors so that setting up the text level shortcuts is handled by the post editor. * removes the old hook * adds the text level keyboard shortcuts to the help modal of the post editor * Removes the hook based approach and inlines every shortcut definition and handler into edit post shortcut conf file. Co-authored-by: Daniel Richards <677833+talldan@users.noreply.github.com> * updated snapshot for keyboard help modal * Refactores the shortcut handler to be a bit quicker returning early for multiselection and avoiding an extra state select. Co-authored-by: Daniel Richards <677833+talldan@users.noreply.github.com> Co-authored-by: Daniel Richards <677833+talldan@users.noreply.github.com> Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Co-authored-by: Dave Smith <444434+getdave@users.noreply.github.com> --- .../keyboard-shortcut-help-modal/config.js | 10 +++ .../test/__snapshots__/index.js.snap | 70 +++++++++++++++++++ .../components/keyboard-shortcuts/index.js | 67 ++++++++++++++++++ packages/keycodes/src/index.js | 2 +- 4 files changed, 148 insertions(+), 1 deletion(-) diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js index 7e098a87be2cd..fd11d14e08cde 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/config.js @@ -36,4 +36,14 @@ export const textFormattingShortcuts = [ keyCombination: { modifier: 'access', character: 'x' }, description: __( 'Make the selected text inline code.' ), }, + { + keyCombination: { modifier: 'access', character: '0' }, + description: __( 'Convert the current heading to a paragraph.' ), + }, + { + keyCombination: { modifier: 'access', character: '1-6' }, + description: __( + 'Convert the current paragraph or heading to a heading of level 1 to 6.' + ), + }, ]; diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap index 7cc2c124e6fbb..7b2ba852ad5aa 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/test/__snapshots__/index.js.snap @@ -821,6 +821,76 @@ exports[`KeyboardShortcutHelpModal should match snapshot when the modal is activ +
  • +
    + Convert the current heading to a paragraph. +
    +
    + + + Shift + + + + + Alt + + + + + 0 + + +
    +
  • +
  • +
    + Convert the current paragraph or heading to a heading of level 1 to 6. +
    +
    + + + Shift + + + + + Alt + + + + + 1-6 + + +
    +
  • diff --git a/packages/edit-post/src/components/keyboard-shortcuts/index.js b/packages/edit-post/src/components/keyboard-shortcuts/index.js index df5205de1c94a..d3d6569e398a4 100644 --- a/packages/edit-post/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-post/src/components/keyboard-shortcuts/index.js @@ -12,6 +12,7 @@ import { store as editorStore } from '@wordpress/editor'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as noticesStore } from '@wordpress/notices'; import { store as preferencesStore } from '@wordpress/preferences'; +import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -53,6 +54,35 @@ function KeyboardShortcuts() { closeGeneralSidebar(); }; + const { replaceBlocks } = useDispatch( blockEditorStore ); + const { getBlockName, getSelectedBlockClientId, getBlockAttributes } = + useSelect( blockEditorStore ); + + const handleTextLevelShortcut = ( event, level ) => { + event.preventDefault(); + const destinationBlockName = + level === 0 ? 'core/paragraph' : 'core/heading'; + const currentClientId = getSelectedBlockClientId(); + if ( currentClientId === null ) { + return; + } + const blockName = getBlockName( currentClientId ); + if ( blockName !== 'core/paragraph' && blockName !== 'core/heading' ) { + return; + } + const currentAttributes = getBlockAttributes( currentClientId ); + const { content: currentContent, align: currentAlign } = + currentAttributes; + replaceBlocks( + currentClientId, + createBlock( destinationBlockName, { + level, + content: currentContent, + align: currentAlign, + } ) + ); + }; + useEffect( () => { registerShortcut( { name: 'core/edit-post/toggle-mode', @@ -149,6 +179,28 @@ function KeyboardShortcuts() { character: 'h', }, } ); + + registerShortcut( { + name: `core/block-editor/transform-heading-to-paragraph`, + category: 'block-library', + description: __( 'Transform heading to paragraph.' ), + keyCombination: { + modifier: 'access', + character: `0`, + }, + } ); + + [ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => { + registerShortcut( { + name: `core/block-editor/transform-paragraph-to-heading-${ level }`, + category: 'block-library', + description: __( 'Transform paragraph to heading.' ), + keyCombination: { + modifier: 'access', + character: `${ level }`, + }, + } ); + } ); }, [] ); useShortcut( @@ -202,6 +254,21 @@ function KeyboardShortcuts() { setIsListViewOpened( ! isListViewOpened() ) ); + useShortcut( + 'core/block-editor/transform-heading-to-paragraph', + ( event ) => handleTextLevelShortcut( event, 0 ) + ); + + [ 1, 2, 3, 4, 5, 6 ].forEach( ( level ) => { + //the loop is based off on a constant therefore + //the hook will execute the same way every time + //eslint-disable-next-line react-hooks/rules-of-hooks + useShortcut( + `core/block-editor/transform-paragraph-to-heading-${ level }`, + ( event ) => handleTextLevelShortcut( event, level ) + ); + } ); + return null; } diff --git a/packages/keycodes/src/index.js b/packages/keycodes/src/index.js index 4d902f4ba4e27..5bb776127d95e 100644 --- a/packages/keycodes/src/index.js +++ b/packages/keycodes/src/index.js @@ -237,7 +237,7 @@ export const displayShortcutList = mapValues( modifiers, ( modifier ) => { // so override the rule to allow symbols used for shortcuts. // see: https://github.com/blakeembrey/change-case#options const capitalizedCharacter = capitalCase( character, { - stripRegexp: /[^A-Z0-9`,\.\\]/gi, + stripRegexp: /[^A-Z0-9`,\.\\\-]/gi, } ); return [ ...modifierKeys, capitalizedCharacter ]; From af37717eb45d0a9775c4f1ebfbc5d4309b69b702 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Tue, 15 Nov 2022 17:25:50 +0900 Subject: [PATCH 004/380] Spacing visualizer: Fix display of unexpected visualizer for certain mouse actions (#45739) --- packages/block-editor/src/hooks/margin.js | 7 ++++--- packages/block-editor/src/hooks/padding.js | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/hooks/margin.js b/packages/block-editor/src/hooks/margin.js index d046685a23323..8491ab41dfe63 100644 --- a/packages/block-editor/src/hooks/margin.js +++ b/packages/block-editor/src/hooks/margin.js @@ -217,14 +217,15 @@ export function MarginVisualizer( { clientId, attributes, forceShow } ) { setIsActive( true ); valueRef.current = margin; - clearTimer(); - timeoutRef.current = setTimeout( () => { setIsActive( false ); }, 400 ); } - return () => clearTimer(); + return () => { + setIsActive( false ); + clearTimer(); + }; }, [ margin, forceShow ] ); if ( ! isActive && ! forceShow ) { diff --git a/packages/block-editor/src/hooks/padding.js b/packages/block-editor/src/hooks/padding.js index 007bc799f9bf8..0c039e637619f 100644 --- a/packages/block-editor/src/hooks/padding.js +++ b/packages/block-editor/src/hooks/padding.js @@ -206,14 +206,15 @@ export function PaddingVisualizer( { clientId, attributes, forceShow } ) { setIsActive( true ); valueRef.current = padding; - clearTimer(); - timeoutRef.current = setTimeout( () => { setIsActive( false ); }, 400 ); } - return () => clearTimer(); + return () => { + setIsActive( false ); + clearTimer(); + }; }, [ padding, forceShow ] ); if ( ! isActive && ! forceShow ) { From 977f052bdbff3d306f8f9932d6d821b31a308ff0 Mon Sep 17 00:00:00 2001 From: chad1008 <13856531+chad1008@users.noreply.github.com> Date: Tue, 15 Nov 2022 06:16:57 -0500 Subject: [PATCH 005/380] Components: Add `exhaustive-deps` eslint rule (#41166) * Components: set eslint `react-hooks/exhaustive-deps` to `warn` * Components: move exhaustive-deps eslint rule to main plugin .eslintrc file * Update .eslintrc.js Co-authored-by: Marco Ciampini * add missing comma * Switch from warn to error * update CHANGELOG Co-authored-by: Marco Ciampini --- .eslintrc.js | 7 +++++++ packages/components/CHANGELOG.md | 1 + 2 files changed, 8 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 38992de398e51..22cb9209b2e9e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -322,6 +322,13 @@ module.exports = { ], }, }, + { + files: [ 'packages/components/src/**/*.[tj]s?(x)' ], + excludedFiles: [ ...developmentFiles ], + rules: { + 'react-hooks/exhaustive-deps': 'error', + }, + }, { files: [ 'packages/jest*/**/*.js', '**/test/**/*.js' ], excludedFiles: [ 'test/e2e/**/*.js' ], diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 11bae78e235e2..fe144c59ec1bb 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -28,6 +28,7 @@ - `Draggable`: Convert to TypeScript ([#45471](https://github.com/WordPress/gutenberg/pull/45471)). - `MenuGroup`: Convert to TypeScript ([#45617](https://github.com/WordPress/gutenberg/pull/45617)). - `useCx`: fix story to satisfy the `react-hooks/exhaustive-deps` eslint rule ([#45614](https://github.com/WordPress/gutenberg/pull/45614)) +- Activate the `react-hooks/exhuastive-deps` eslint rule for the Components package ([#41166](https://github.com/WordPress/gutenberg/pull/41166)) ### Experimental From 8849703ddf414ae1e1e4ac06b91fd299984d0360 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 15 Nov 2022 12:51:06 +0100 Subject: [PATCH 006/380] Fix the navigate regions focus style (#45369) * Fix navigateRegions focus style first pass. * Fix navigateRegions focus style in the Site editor first pass. * Use grid items to establish new stacking context. * Abstract navigable region and make CSS more robust. * Simplify JS and add readme. * Clean up CSS and fix edge cases. * Use new theming accent color. --- packages/base-styles/_z-index.scss | 2 + .../higher-order/navigate-regions/style.scss | 92 +++++++++++++++---- .../src/components/layout/style.scss | 13 ++- .../src/components/editor/style.scss | 8 +- .../navigation-panel/style.scss | 7 -- .../complementary-area-context/index.js | 12 +++ packages/interface/src/components/index.js | 1 + .../components/interface-skeleton/index.js | 82 +++++++---------- .../components/interface-skeleton/style.scss | 23 ++++- .../src/components/navigable-region/README.md | 38 ++++++++ .../src/components/navigable-region/index.js | 32 +++++++ 11 files changed, 225 insertions(+), 85 deletions(-) create mode 100644 packages/interface/complementary-area-context/index.js create mode 100644 packages/interface/src/components/navigable-region/README.md create mode 100644 packages/interface/src/components/navigable-region/index.js diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 0cec716ac360d..14305a9361197 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -187,6 +187,8 @@ $z-layers: ( // Appear under the topbar. ".customize-widgets__block-toolbar": 7, + + ".is-focusing-regions [role='region']:focus .interface-navigable-region__stacker": -1, ); @function z-index( $key ) { diff --git a/packages/components/src/higher-order/navigate-regions/style.scss b/packages/components/src/higher-order/navigate-regions/style.scss index 739cb1a9b6175..86f089b68ba3d 100644 --- a/packages/components/src/higher-order/navigate-regions/style.scss +++ b/packages/components/src/higher-order/navigate-regions/style.scss @@ -3,30 +3,82 @@ position: relative; } -.is-focusing-regions [role="region"] { - // For browsers that don't support outline-offset (IE11). - &:focus::after { - content: ""; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - pointer-events: none; - outline: 4px solid transparent; // Shown in Windows High Contrast mode. - box-shadow: inset 0 0 0 4px $components-color-accent; +.is-focusing-regions { + [role="region"]:focus { + outline: 4px solid $components-color-accent; + outline-offset: -4px; + + .interface-navigable-region__stacker { + position: relative; + z-index: z-index(".is-focusing-regions [role='region']:focus .interface-navigable-region__stacker"); + } + } + + // Fixes for edge cases. + // Some of the regions are currently used for layout purposes as 'interface skeleton' + // items. When they're absolutely positioned or when they contain absolutely + // positioned elements, they may have no size therefore the focus style is not + // visible. For the future, it's important to take into consideration that + // the navigable regions should always have a computed size. For now, we can + // fix some edge cases but these CSS rules should be later removed in favor of + // a more abstracted approach to make the navigabel regions focus style work + // regardles of the CSS used on other components. + + // Header top bar when Distraction free mode is on. + &.is-distraction-free .interface-interface-skeleton__header { + .interface-navigable-region__stacker, + .edit-post-header { + outline: inherit; + outline-offset: inherit; + } } - @supports ( outline-offset: 1px ) { - &:focus::after { - content: none; + // Sidebar toggle button shown when navigating regions. + .interface-interface-skeleton__sidebar { + .interface-navigable-region__stacker, + .edit-post-layout__toggle-sidebar-panel { + outline: inherit; + outline-offset: inherit; } + } - &:focus { - outline-style: solid; - outline-color: $components-color-accent; - outline-width: 4px; - outline-offset: -4px; + // Publish sidebar toggle button shown when navigating regions. + .interface-interface-skeleton__actions { + .interface-navigable-region__stacker, + .edit-post-layout__toggle-publish-panel { + outline: inherit; + outline-offset: inherit; } } + + // Publish sidebar. + [role="region"].interface-interface-skeleton__actions:focus .editor-post-publish-panel { + outline: 4px solid $components-color-accent; + outline-offset: -4px; + } + + // Edit site Navigation Drawer. + .interface-interface-skeleton__drawer { + z-index: z-index(".edit-site-navigation-toggle"); + + .interface-navigable-region__stacker, + .edit-site-navigation-toggle { + outline: inherit; + outline-offset: inherit; + } + + .edit-site-navigation-toggle.is-open { + outline: none; + } + + .edit-site-navigation-toggle__button { + z-index: -1; + } + } +} + +// Fixes for edge cases. +// Edit site Drawer. +.interface-interface-skeleton__drawer .interface-navigable-region__stacker { + height: 100%; } diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index f785b695489e3..473dae279473e 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -60,7 +60,6 @@ justify-content: center; } - .edit-post-layout__toggle-publish-panel, .edit-post-layout__toggle-sidebar-panel, .edit-post-layout__toggle-entities-saved-states-panel { @@ -70,6 +69,7 @@ bottom: auto; left: auto; right: 0; + box-sizing: border-box; width: $sidebar-width; background-color: $white; border: 1px dotted $gray-300; @@ -77,7 +77,18 @@ padding: $grid-unit-30; display: flex; justify-content: center; +} + +.edit-post-layout__toggle-sidebar-panel { + .interface-interface-skeleton__sidebar:focus &, + .interface-interface-skeleton__sidebar:focus-within & { + top: auto; + bottom: 0; + } +} +.edit-post-layout__toggle-entities-saved-states-panel, +.edit-post-layout__toggle-publish-panel { .interface-interface-skeleton__actions:focus &, .interface-interface-skeleton__actions:focus-within & { top: auto; diff --git a/packages/edit-site/src/components/editor/style.scss b/packages/edit-site/src/components/editor/style.scss index 580268d00e967..80a41bbb30be0 100644 --- a/packages/edit-site/src/components/editor/style.scss +++ b/packages/edit-site/src/components/editor/style.scss @@ -7,16 +7,10 @@ html.wp-toolbar { } .edit-site-editor__toggle-save-panel { - z-index: z-index(".edit-site-editor__toggle-save-panel"); - position: fixed !important; // Need to override the default relative positioning - top: -9999em; - bottom: auto; - left: auto; - right: 0; + box-sizing: border-box; width: $sidebar-width; background-color: $white; border: 1px dotted $gray-300; - height: auto !important; // Need to override the default sidebar positioning padding: $grid-unit-30; display: flex; justify-content: center; diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/style.scss b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/style.scss index 6bab3a634fe78..a66a90876c8d4 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/style.scss +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/style.scss @@ -7,13 +7,6 @@ color: $white; transition: width 100ms linear; @include reduce-motion("transition"); - - // Footer is visible from medium so we subtract footer's height - .interface-interface-skeleton.has-footer & { - @include break-medium() { - height: calc(100% - #{$button-size-small + $border-width}); - } - } } .edit-site-navigation-panel__inner { diff --git a/packages/interface/complementary-area-context/index.js b/packages/interface/complementary-area-context/index.js new file mode 100644 index 0000000000000..bdfc6869e1e51 --- /dev/null +++ b/packages/interface/complementary-area-context/index.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { withPluginContext } from '@wordpress/plugins'; + +export default withPluginContext( ( context, ownProps ) => { + return { + icon: ownProps.icon || context.icon, + identifier: + ownProps.identifier || `${ context.name }/${ ownProps.name }`, + }; +} ); diff --git a/packages/interface/src/components/index.js b/packages/interface/src/components/index.js index aa03fa34ae8c6..c9c2d09b3b3ab 100644 --- a/packages/interface/src/components/index.js +++ b/packages/interface/src/components/index.js @@ -10,3 +10,4 @@ export { default as PreferencesModal } from './preferences-modal'; export { default as PreferencesModalTabs } from './preferences-modal-tabs'; export { default as PreferencesModalSection } from './preferences-modal-section'; export { default as ___unstablePreferencesModalBaseOption } from './preferences-modal-base-option'; +export { default as NavigableRegion } from './navigable-region'; diff --git a/packages/interface/src/components/interface-skeleton/index.js b/packages/interface/src/components/interface-skeleton/index.js index 766ef73a2fcf5..560943aa5d963 100644 --- a/packages/interface/src/components/interface-skeleton/index.js +++ b/packages/interface/src/components/interface-skeleton/index.js @@ -7,13 +7,15 @@ import classnames from 'classnames'; * WordPress dependencies */ import { forwardRef, useEffect } from '@wordpress/element'; -import { - __unstableUseNavigateRegions as useNavigateRegions, - __unstableMotion as motion, -} from '@wordpress/components'; +import { __unstableUseNavigateRegions as useNavigateRegions } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMergeRefs } from '@wordpress/compose'; +/** + * Internal dependencies + */ +import NavigableRegion from '../navigable-region'; + function useHTMLClass( className ) { useEffect( () => { const element = @@ -89,39 +91,35 @@ function InterfaceSkeleton( ) } > { !! drawer && ( -
    { drawer } -
    + ) }
    { !! header && isDistractionFree && ( - { header } - + ) } { !! header && ! isDistractionFree && ( -
    { header } -
    + ) } { isDistractionFree && (
    @@ -130,59 +128,49 @@ function InterfaceSkeleton( ) }
    { !! secondarySidebar && ( -
    { secondarySidebar } -
    + ) } { !! notices && (
    { notices }
    ) } -
    { content } -
    + { !! sidebar && ( -
    { sidebar } -
    + ) } { !! actions && ( -
    { actions } -
    + ) }
    { !! footer && ( -
    { footer } -
    + ) }
    ); diff --git a/packages/interface/src/components/interface-skeleton/style.scss b/packages/interface/src/components/interface-skeleton/style.scss index 12c14cfcd2e31..a877371f8d509 100644 --- a/packages/interface/src/components/interface-skeleton/style.scss +++ b/packages/interface/src/components/interface-skeleton/style.scss @@ -72,6 +72,10 @@ html.interface-interface-skeleton__html-container { } .interface-interface-skeleton__content { + .interface-navigable-region__stacker { + flex-grow: 1; + } + flex-grow: 1; // Treat as flex container to allow children to grow to occupy full @@ -93,7 +97,6 @@ html.interface-interface-skeleton__html-container { .interface-interface-skeleton__secondary-sidebar, .interface-interface-skeleton__sidebar { - display: block; flex-shrink: 0; position: absolute; z-index: z-index(".interface-interface-skeleton__sidebar"); @@ -107,8 +110,14 @@ html.interface-interface-skeleton__html-container { // On Mobile the header is fixed to keep HTML as scrollable. @include break-medium() { position: relative !important; - z-index: z-index(".interface-interface-skeleton__sidebar {greater than small}"); width: auto; // Keep the sidebar width flexible. + + // Set this z-index only when the sidebar is opened. When it's closed, the + // button to open the sidebar that is shown when navigating regions needs to + // be above the footer. See `edit-post-layout__toggle-sidebar-panel`. + .is-sidebar-opened & { + z-index: z-index(".interface-interface-skeleton__sidebar {greater than small}"); + } } } @@ -173,8 +182,16 @@ html.interface-interface-skeleton__html-container { width: $sidebar-width; color: $gray-900; - &:focus { + &:focus, + &:focus-within { top: auto; bottom: 0; } } + +// Footer is visible from medium so we subtract footer's height +.interface-interface-skeleton.has-footer .interface-interface-skeleton__drawer { + @include break-medium() { + height: calc(100% - #{$button-size-small + $border-width}); + } +} diff --git a/packages/interface/src/components/navigable-region/README.md b/packages/interface/src/components/navigable-region/README.md new file mode 100644 index 0000000000000..6f98d17d79c45 --- /dev/null +++ b/packages/interface/src/components/navigable-region/README.md @@ -0,0 +1,38 @@ +# NavigableRegion + +`NavigableRegion` renders an ARIA landmark region that's meant to be used together with the `useNavigateRegions` component from the `@wordpress/components` package. The ARIA landmark is a `div` element with a `role="region"` attribute and an `aria-label` attribute. It's made focusable by the means of a `tabindex="-1"` attribute so that `useNavigateRegions` can set focus on it and allow keyboard navigation through the regions in the editor. + +It also renders a child `div` element that is responsible to set a negative `z-index` stack level, to make sure the focus style is always visible, regardless of other elements that may cut-off the focus style outline otherwise. + +It can use a CSS animation via the `motion` component. + +## Props + +### children + +The component that should be rendered as content. + +- Type: React Element +- Required: Yes + +### className + +The CSS class that will be added to the classes of the wrapper div. + +- Type: `String` +- Required: No + +### ariaLabel + +A meaningful name for the ARIA landmark region. + +- Type: `String` +- Required: Yes + +### motionProps + +Properties of `motionProps` object will be used by the `motion` component to set a CSS animation on the wrapper div. + +- Type: `Object` +- Required: No +- Default: `{}` diff --git a/packages/interface/src/components/navigable-region/index.js b/packages/interface/src/components/navigable-region/index.js new file mode 100644 index 0000000000000..3b86b856b1d3f --- /dev/null +++ b/packages/interface/src/components/navigable-region/index.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __unstableMotion as motion } from '@wordpress/components'; + +export default function NavigableRegion( { + children, + className, + ariaLabel, + motionProps = {}, +} ) { + const Tag = Object.keys( motionProps ).length ? motion.div : 'div'; + + return ( + +
    + { children } +
    +
    + ); +} From 911d01a9b4df2bc9ffb318d7add9d9f8a4a20a6e Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 15 Nov 2022 12:59:18 +0100 Subject: [PATCH 007/380] Fix the Save buttons labelling and tooltip (#43952) * Fix PostSavedState button labelling and tooltip. * Use isDisabled instead of isSavedState. * Show tooltip and shortcut for the Site editor Save button. * Update snapshot. * Fix aria-label and update snapshot. * Make the tooltip always show. * Apply show tooltip fix to the Site editor Save button. --- .../edit-post/src/components/header/style.scss | 9 ++------- .../src/components/save-button/index.js | 18 +++++++++++++++++- .../src/components/post-saved-state/index.js | 18 +++++++++++++++--- .../test/__snapshots__/index.js.snap | 2 +- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/edit-post/src/components/header/style.scss b/packages/edit-post/src/components/header/style.scss index 3ed61c132ce3f..160543684a702 100644 --- a/packages/edit-post/src/components/header/style.scss +++ b/packages/edit-post/src/components/header/style.scss @@ -146,16 +146,11 @@ } } - // The post saved state button has a custom label only on small breakpoint - .editor-post-save-draft.editor-post-save-draft { + .editor-post-save-draft.editor-post-save-draft, + .editor-post-saved-state.editor-post-saved-state { &::after { content: none; } - @include break-small { - &::after { - content: attr(aria-label); - } - } } } diff --git a/packages/edit-site/src/components/save-button/index.js b/packages/edit-site/src/components/save-button/index.js index 37815ec950030..28c63d7ef56c1 100644 --- a/packages/edit-site/src/components/save-button/index.js +++ b/packages/edit-site/src/components/save-button/index.js @@ -5,6 +5,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { store as coreStore } from '@wordpress/core-data'; +import { displayShortcut } from '@wordpress/keycodes'; /** * Internal dependencies @@ -29,6 +30,8 @@ export default function SaveButton() { const disabled = ! isDirty || isSaving; + const label = __( 'Save' ); + return ( ); } diff --git a/packages/editor/src/components/post-saved-state/index.js b/packages/editor/src/components/post-saved-state/index.js index 1dc3f348e1e04..7d57b2a630e4e 100644 --- a/packages/editor/src/components/post-saved-state/index.js +++ b/packages/editor/src/components/post-saved-state/index.js @@ -151,10 +151,22 @@ export default function PostSavedState( { : undefined } onClick={ isDisabled ? undefined : () => savePost() } - shortcut={ displayShortcut.primary( 's' ) } - variant={ isLargeViewport ? 'tertiary' : undefined } + /* + * We want the tooltip to show the keyboard shortcut only when the + * button does something, i.e. when it's not disabled. + */ + shortcut={ isDisabled ? undefined : displayShortcut.primary( 's' ) } + /* + * Displaying the keyboard shortcut conditionally makes the tooltip + * itself show conditionally. This would trigger a full-rerendering + * of the button that we want to avoid. By setting `showTooltip`, + & the tooltip is always rendered even when there's no keyboard shortcut. + */ + showTooltip + variant="tertiary" icon={ isLargeViewport ? undefined : cloudUpload } - label={ showIconLabels ? undefined : label } + // Make sure the aria-label has always a value, as the default `text` is undefined on small screens. + label={ text || label } aria-disabled={ isDisabled } > { isSavedState && } diff --git a/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap index 7f14bbc722589..d24ed1370537a 100644 --- a/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap @@ -4,7 +4,7 @@ exports[`PostSavedState returns a disabled button if the post is not saveable 1` } ); @@ -33,11 +33,11 @@ describe( 'useDisabled', () => { } it( 'will disable all fields', () => { - const { container } = render( ); + render( ); const input = screen.getByRole( 'textbox' ); const link = screen.getByRole( 'link' ); - const p = container.querySelector( 'p' ); + const p = screen.getByRole( 'document' ); expect( input ).toHaveAttribute( 'inert', 'true' ); expect( link ).toHaveAttribute( 'inert', 'true' ); From 7d99735051286b62adc891d2e11c40e23959c895 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 16 Nov 2022 10:42:25 +0100 Subject: [PATCH 020/380] Fix the editor area height. (#45799) --- .../interface/src/components/interface-skeleton/style.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/interface/src/components/interface-skeleton/style.scss b/packages/interface/src/components/interface-skeleton/style.scss index a877371f8d509..6e68a2d1c93ff 100644 --- a/packages/interface/src/components/interface-skeleton/style.scss +++ b/packages/interface/src/components/interface-skeleton/style.scss @@ -73,7 +73,13 @@ html.interface-interface-skeleton__html-container { .interface-interface-skeleton__content { .interface-navigable-region__stacker { + // It's a flex item within a flex container with flex-direction column. + // Make sure it takes all the available height. flex-grow: 1; + // Allow its flex items (the visual editor area) to take all the available + // height by making it also a flex container. + display: flex; + flex-direction: column; } flex-grow: 1; @@ -92,7 +98,6 @@ html.interface-interface-skeleton__html-container { // to "bleed" through the header. // See https://github.com/WordPress/gutenberg/issues/32631 z-index: z-index(".interface-interface-skeleton__content"); - } .interface-interface-skeleton__secondary-sidebar, From c9f6b05a74d6a35891dfd845c8aeb5ccb10c78dc Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 16 Nov 2022 13:03:00 +0200 Subject: [PATCH 021/380] Fix inserter tab panel content buttons' position (#45800) --- .../interface/src/components/interface-skeleton/style.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/interface/src/components/interface-skeleton/style.scss b/packages/interface/src/components/interface-skeleton/style.scss index 6e68a2d1c93ff..7897dcf824cce 100644 --- a/packages/interface/src/components/interface-skeleton/style.scss +++ b/packages/interface/src/components/interface-skeleton/style.scss @@ -135,6 +135,10 @@ html.interface-interface-skeleton__html-container { } .interface-interface-skeleton__secondary-sidebar { + .interface-navigable-region__stacker { + height: 100%; + } + @include break-medium() { border-right: $border-width solid $gray-200; } From 768e01aa5c3473dfb2fd6820c91a77049b813a4c Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 16 Nov 2022 13:18:48 +0200 Subject: [PATCH 022/380] [Pattern Directory]: Add categories endpoint (#45749) * [Pattern Directory]: Add categories endpoint * fix linting issues * add `locale` to the endpoint and transient key * address feedback --- ...-rest-pattern-directory-controller-6-2.php | 125 +++++++++++++++++- ...rest-pattern-directory-controller-test.php | 52 ++++++-- 2 files changed, 167 insertions(+), 10 deletions(-) diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php index cc44c37b4d7c9..ab845f3c38de1 100644 --- a/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php +++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php @@ -10,6 +10,127 @@ * Controller which provides REST endpoint for block patterns from wordpress.org/patterns. */ class Gutenberg_REST_Pattern_Directory_Controller_6_2 extends Gutenberg_REST_Pattern_Directory_Controller_6_0 { + /** + * Registers the necessary REST API routes. + * + * @since 5.8.0 + * @since 6.2.0 Added pattern directory categories endpoint. + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/categories', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_pattern_categories' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + ), + ) + ); + + parent::register_routes(); + } + + /** + * Retrieve block patterns categories. + * + * @since 6.2.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_pattern_categories( $request ) { + $query_args = array( 'locale' => get_user_locale() ); + $transient_key = 'wp_remote_block_pattern_categories_' . md5( serialize( $query_args ) ); + + /** + * Use network-wide transient to improve performance. The locale is the only site + * configuration that affects the response, and it's included in the transient key. + */ + $raw_pattern_categories = get_site_transient( $transient_key ); + + if ( ! $raw_pattern_categories ) { + $api_url = 'http://api.wordpress.org/patterns/1.0/?categories&' . build_query( $query_args ); + if ( wp_http_supports( array( 'ssl' ) ) ) { + $api_url = set_url_scheme( $api_url, 'https' ); + } + + /** + * Default to a short TTL, to mitigate cache stampedes on high-traffic sites. + * This assumes that most errors will be short-lived, e.g., packet loss that causes the + * first request to fail, but a follow-up one will succeed. The value should be high + * enough to avoid stampedes, but low enough to not interfere with users manually + * re-trying a failed request. + */ + $cache_ttl = 5; + $wporg_response = wp_remote_get( $api_url ); + $raw_pattern_categories = json_decode( wp_remote_retrieve_body( $wporg_response ) ); + if ( is_wp_error( $wporg_response ) ) { + $raw_pattern_categories = $wporg_response; + + } elseif ( ! is_array( $raw_pattern_categories ) ) { + // HTTP request succeeded, but response data is invalid. + $raw_pattern_categories = new WP_Error( + 'pattern_directory_api_failed', + sprintf( + /* translators: %s: Support forums URL. */ + __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the support forums.', 'gutenberg' ), + __( 'https://wordpress.org/support/forums/', 'gutenberg' ) + ), + array( + 'response' => wp_remote_retrieve_body( $wporg_response ), + ) + ); + + } else { + // Response has valid data. + $cache_ttl = HOUR_IN_SECONDS; + } + + set_site_transient( $transient_key, $raw_pattern_categories, $cache_ttl ); + } + + if ( is_wp_error( $raw_pattern_categories ) ) { + $raw_pattern_categories->add_data( array( 'status' => 500 ) ); + + return $raw_pattern_categories; + } + + $response = array(); + + if ( $raw_pattern_categories ) { + foreach ( $raw_pattern_categories as $category ) { + $response[] = $this->prepare_response_for_collection( + $this->prepare_pattern_category_for_response( $category, $request ) + ); + } + } + + return new WP_REST_Response( $response ); + } + + /** + * Prepare a raw block pattern category before it gets output in a REST API response. + * + * @since 6.2.0 + * + * @param object $item Raw pattern category from api.wordpress.org, before any changes. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_pattern_category_for_response( $item, $request ) { + $raw_pattern_category = array( + 'id' => absint( $item->id ), + 'name' => sanitize_text_field( $item->name ), + 'slug' => sanitize_title_with_dashes( $item->slug ), + ); + + $prepared_pattern_category = $this->add_additional_fields_to_object( $raw_pattern_category, $request ); + + return new WP_REST_Response( $prepared_pattern_category ); + } + /** * Search and retrieve block patterns metadata * @@ -48,7 +169,7 @@ public function get_items( $request ) { $transient_key = $this->get_transient_key( $query_args ); - /* + /** * Use network-wide transient to improve performance. The locale is the only site * configuration that affects the response, and it's included in the transient key. */ @@ -60,7 +181,7 @@ public function get_items( $request ) { $api_url = set_url_scheme( $api_url, 'https' ); } - /* + /** * Default to a short TTL, to mitigate cache stampedes on high-traffic sites. * This assumes that most errors will be short-lived, e.g., packet loss that causes the * first request to fail, but a follow-up one will succeed. The value should be high diff --git a/phpunit/class-wp-rest-pattern-directory-controller-test.php b/phpunit/class-wp-rest-pattern-directory-controller-test.php index bf3bb8c4bf188..c05e6b4be114f 100644 --- a/phpunit/class-wp-rest-pattern-directory-controller-test.php +++ b/phpunit/class-wp-rest-pattern-directory-controller-test.php @@ -53,7 +53,7 @@ public static function wpSetUpBeforeClass( $factory ) { self::$http_request_urls = array(); - static::$controller = new WP_REST_Pattern_Directory_Controller(); + static::$controller = new Gutenberg_REST_Pattern_Directory_Controller_6_2(); } public static function wpTearDownAfterClass() { @@ -68,6 +68,49 @@ public function tear_down() { parent::tear_down(); } + /** + * @covers WP_REST_Pattern_Directory_Controller::register_routes + * + * @since 5.8.0 + * @since 6.2.0 Added pattern directory categories endpoint. + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + + $this->assertArrayHasKey( '/wp/v2/pattern-directory/patterns', $routes ); + $this->assertArrayHasKey( '/wp/v2/pattern-directory/categories', $routes ); + } + + /** + * @covers WP_REST_Pattern_Directory_Controller::prepare_pattern_category_for_response + * + * @since 6.2.0 + */ + public function test_prepare_pattern_category_for_response() { + $raw_categories = array( + (object) array( + 'id' => 3, + 'name' => 'Columns', + 'slug' => 'columns', + 'description' => 'A description', + ), + ); + + $prepared_category = static::$controller->prepare_response_for_collection( + static::$controller->prepare_pattern_category_for_response( $raw_categories[0], new WP_REST_Request() ) + ); + + $this->assertSame( + array( + 'id' => 3, + 'name' => 'Columns', + 'slug' => 'columns', + ), + $prepared_category + ); + } + + /** * Tests if the provided query args are passed through to the wp.org API. * @@ -168,13 +211,6 @@ function ( $preempt, $args, $url ) { ); } - /** - * @doesNotPerformAssertions - */ - public function test_register_routes() { - // Covered by the core test. - } - /** * @doesNotPerformAssertions */ From 7ceca9146b8700d81b9f326335594d635ef00022 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Wed, 16 Nov 2022 22:27:43 +0900 Subject: [PATCH 023/380] Plugin: Navigation menu doesn't appear when hamburger clicked on (#45773) --- lib/compat/wordpress-6.1/blocks.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.1/blocks.php b/lib/compat/wordpress-6.1/blocks.php index cd61b70a07f09..3aa790dadcb1c 100644 --- a/lib/compat/wordpress-6.1/blocks.php +++ b/lib/compat/wordpress-6.1/blocks.php @@ -71,12 +71,12 @@ function gutenberg_block_type_metadata_view_script( $settings, $metadata ) { ! isset( $metadata['viewScript'] ) || ! empty( $settings['view_script'] ) || ! isset( $metadata['file'] ) || - ! str_starts_with( $metadata['file'], gutenberg_dir_path() ) + ! str_starts_with( $metadata['file'], wp_normalize_path( gutenberg_dir_path() ) ) ) { return $settings; } - $view_script_path = realpath( dirname( $metadata['file'] ) . '/' . remove_block_asset_path_prefix( $metadata['viewScript'] ) ); + $view_script_path = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . remove_block_asset_path_prefix( $metadata['viewScript'] ) ) ); if ( file_exists( $view_script_path ) ) { $view_script_id = str_replace( array( '.min.js', '.js' ), '', basename( remove_block_asset_path_prefix( $metadata['viewScript'] ) ) ); @@ -90,7 +90,7 @@ function gutenberg_block_type_metadata_view_script( $settings, $metadata ) { $view_script_version = isset( $view_asset['version'] ) ? $view_asset['version'] : false; $result = wp_register_script( $view_script_handle, - gutenberg_url( str_replace( gutenberg_dir_path(), '', $view_script_path ) ), + gutenberg_url( str_replace( wp_normalize_path( gutenberg_dir_path() ), '', $view_script_path ) ), $view_script_dependencies, $view_script_version ); From 5e10c2b7ff075f5974a1b630b764489e366a7260 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 16 Nov 2022 17:08:14 +0200 Subject: [PATCH 024/380] [Inserter]: Update pattern explorer button css (#45735) * [Inserter]: Update pattern explorer button css * add comment --- .../src/components/inserter/block-patterns-tab.js | 6 ------ packages/block-editor/src/components/inserter/style.scss | 8 ++++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab.js b/packages/block-editor/src/components/inserter/block-patterns-tab.js index fd96923114d0e..83d7b899f181f 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab.js @@ -215,12 +215,6 @@ function BlockPatternsTabs( { ) ) } - -
    -
    '; const HTML_WITH_CLASSES = '
    Text
    '; const HTML_MALFORMED = '
    Back to notifications
    '; @@ -951,7 +932,7 @@ public function data_script_state() { public function test_next_tag_ignores_the_contents_of_a_rcdata_tag( $rcdata_then_div, $rcdata_tag ) { $p = new WP_HTML_Tag_Processor( $rcdata_then_div ); $p->next_tag(); - $this->assertSame( $rcdata_tag, $p->get_tag(), "The first found tag was not '$rcdata_tag'" ); + $this->assertSame( strtoupper( $rcdata_tag ), $p->get_tag(), "The first found tag was not '$rcdata_tag'" ); $p->next_tag(); $this->assertSame( 'DIV', $p->get_tag(), "The second found tag was not 'div'" ); } diff --git a/phpunit/html/wp-html-tag-processor-wp-test.php b/phpunit/html/wp-html-tag-processor-wp-test.php deleted file mode 100644 index 41008800d0b75..0000000000000 --- a/phpunit/html/wp-html-tag-processor-wp-test.php +++ /dev/null @@ -1,91 +0,0 @@ - - * $p = new WP_HTML_Tag_Processor( '
    ' ); - * $p->next_tag(); - * $p->set_attribute('class', '" onclick="alert'); - * echo $p; - * //
    - * - * - * To prevent it, `set_attribute` calls `esc_attr()` on its given values. - * - * - *
    - *
    - * - * @ticket 56299 - * - * @dataProvider data_set_attribute_escapable_values - * @covers set_attribute - */ - public function test_set_attribute_prevents_xss( $value_to_set, $expected_result ) { - $p = new WP_HTML_Tag_Processor( '
    ' ); - $p->next_tag(); - $p->set_attribute( 'test', $value_to_set ); - - /* - * Testing the escaping is hard using tools that properly parse - * HTML because they might interpret the escaped values. It's hard - * with tools that don't understand HTML because they might get - * confused by improperly-escaped values. - * - * For this test, since we control the input HTML we're going to - * do what looks like the opposite of what we want to be doing with - * this library but are only doing so because we have full control - * over the content and because we want to look at the raw values. - */ - $match = null; - preg_match( '~^
    $~', $p->get_updated_html(), $match ); - list( , $actual_value ) = $match; - - $this->assertEquals( $actual_value, '"' . $expected_result . '"' ); - } - - /** - * Data provider with HTML attribute values that might need escaping. - */ - public function data_set_attribute_escapable_values() { - return array( - array( '"', '"' ), - array( '"', '"' ), - array( '&', '&' ), - array( '&', '&' ), - array( '€', '€' ), - array( "'", ''' ), - array( '<>', '<>' ), - array( '"";', '&quot";' ), - array( - '" onclick="alert(\'1\');">', - '" onclick="alert('1');"><span onclick=""></span><script>alert("1")</script>', - ), - ); - } - -} From fecc1d2aa66c2fb2c6379fb47de5d7cd71f01f8c Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 17 Nov 2022 11:28:16 +0400 Subject: [PATCH 035/380] Post Featured Image: Only get the post title when rendering alt text (#45835) --- packages/block-library/src/post-featured-image/index.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/block-library/src/post-featured-image/index.php b/packages/block-library/src/post-featured-image/index.php index 068b7eeac6899..dfb03c5a89065 100644 --- a/packages/block-library/src/post-featured-image/index.php +++ b/packages/block-library/src/post-featured-image/index.php @@ -27,12 +27,11 @@ function render_block_core_post_featured_image( $attributes, $content, $block ) $is_link = isset( $attributes['isLink'] ) && $attributes['isLink']; $size_slug = isset( $attributes['sizeSlug'] ) ? $attributes['sizeSlug'] : 'post-thumbnail'; - $post_title = trim( strip_tags( get_the_title( $post_ID ) ) ); $attr = get_block_core_post_featured_image_border_attributes( $attributes ); $overlay_markup = get_block_core_post_featured_image_overlay_element_markup( $attributes ); if ( $is_link ) { - $attr['alt'] = $post_title; + $attr['alt'] = trim( strip_tags( get_the_title( $post_ID ) ) ); } if ( ! empty( $attributes['height'] ) ) { From 476ce0377ab30055534b59837b5302068d27c788 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:51:13 +1000 Subject: [PATCH 036/380] Sidebar: Split block tools into menu, settings, and appearance tabs (#45005) * Add drawer icons to icons package Switch editor sidebar icons to new drawer icons Icon should reflect RTL as well. Update TabPanel to allow icons on TabButtons Add menu group to InspectorControl slot fills Move nav menu controls into InspectorControls menu group Introduce menu, settings & appearance tabs to sidebar Refactor InspectorControlTabs Re-orders the appearance and settings tabs. Also now omits the TabPanel altogether if only a single tab has contents. Make block inspector tabs a Gutenberg experiment A little tidy up Clean up conditional tabs display Remove nav specific menu tab for now Remove Menu inspector controls group Refactor inspector controls tabs to components Remove conditional display of tabs Render no settings or tools messages when no fills Reduce new styles for equal width tabs Add general slot for items applying to block as a whole Move query block new post link to new slot Revert "Move query block new post link to new slot" This reverts commit 12799852648a67cb566112639d4d71e33a2d9834. Revert "Add general slot for items applying to block as a whole" This reverts commit 186276fb42af1a7dd571892b349eaacd34f0a3b0. Tweak no style options message Add changelog for TabPanel updates Remove empty readme until experiment settles Fix copy and paste error * Fix changelog Co-authored-by: Daniel Richards --- lib/experimental/editor-settings.php | 3 + lib/experiments-page.php | 14 ++ .../src/components/block-inspector/index.js | 163 +++++++++--------- .../src/components/block-inspector/style.scss | 10 +- .../advanced-controls-panel.js | 37 ++++ .../inspector-controls-tabs/appearance-tab.js | 75 ++++++++ .../inspector-controls-tabs/index.js | 42 +++++ .../inspector-controls-tabs/settings-tab.js | 43 +++++ .../inspector-controls-tabs/utils.js | 20 +++ packages/components/CHANGELOG.md | 4 + packages/components/src/tab-panel/README.md | 1 + packages/components/src/tab-panel/index.tsx | 5 +- packages/components/src/tab-panel/types.ts | 10 +- .../sidebar/settings-sidebar/index.js | 10 +- .../src/components/sidebar-edit-mode/index.js | 6 +- packages/icons/src/index.js | 2 + packages/icons/src/library/drawer-left.js | 21 +++ packages/icons/src/library/drawer-right.js | 21 +++ 18 files changed, 397 insertions(+), 90 deletions(-) create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/index.js create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/utils.js create mode 100644 packages/icons/src/library/drawer-left.js create mode 100644 packages/icons/src/library/drawer-right.js diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index b0c05544d6b19..862a1bfe2b28c 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -86,6 +86,9 @@ function gutenberg_enable_experiments() { if ( $gutenberg_experiments && array_key_exists( 'gutenberg-off-canvas-navigation-editor', $gutenberg_experiments ) ) { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableOffCanvasNavigationEditor = true', 'before' ); } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-inspector-tabs', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableBlockInspectorTabs = true', 'before' ); + } } add_action( 'admin_init', 'gutenberg_enable_experiments' ); diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 309612664cb9c..1296cdd03b89f 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -52,6 +52,7 @@ function gutenberg_initialize_experiments_settings() { 'id' => 'gutenberg-zoomed-out-view', ) ); + add_settings_field( 'gutenberg-off-canvas-navigation-editor', __( 'Off canvas navigation editor ', 'gutenberg' ), @@ -63,6 +64,7 @@ function gutenberg_initialize_experiments_settings() { 'id' => 'gutenberg-off-canvas-navigation-editor', ) ); + add_settings_field( 'gutenberg-color-randomizer', __( 'Color randomizer ', 'gutenberg' ), @@ -75,6 +77,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-block-inspector-tabs', + __( 'Block inspector tabs ', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Test a new block inspector view splitting settings and appearance controls into tabs', 'gutenberg' ), + 'id' => 'gutenberg-block-inspector-tabs', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 379d8806f2a68..f48819ec4a02b 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -9,9 +9,8 @@ import { store as blocksStore, } from '@wordpress/blocks'; import { - PanelBody, - __experimentalUseSlotFills as useSlotFills, FlexItem, + PanelBody, __experimentalHStack as HStack, __experimentalVStack as VStack, Button, @@ -24,20 +23,19 @@ import { useMemo, useCallback } from '@wordpress/element'; */ import SkipToSelectedBlock from '../skip-to-selected-block'; import BlockCard from '../block-card'; -import { - default as InspectorControls, - InspectorAdvancedControls, -} from '../inspector-controls'; -import BlockStyles from '../block-styles'; import MultiSelectionInspector from '../multi-selection-inspector'; -import DefaultStylePicker from '../default-style-picker'; import BlockVariationTransforms from '../block-variation-transforms'; import useBlockDisplayInformation from '../use-block-display-information'; import { store as blockEditorStore } from '../../store'; import BlockIcon from '../block-icon'; +import BlockStyles from '../block-styles'; +import DefaultStylePicker from '../default-style-picker'; +import { default as InspectorControls } from '../inspector-controls'; +import { default as InspectorControlsTabs } from '../inspector-controls-tabs'; +import AdvancedControls from '../inspector-controls-tabs/advanced-controls-panel'; function useContentBlocks( blockTypes, block ) { - const contenBlocksObjectAux = useMemo( () => { + const contentBlocksObjectAux = useMemo( () => { return blockTypes.reduce( ( result, blockType ) => { if ( blockType.name !== 'core/list-item' && @@ -53,7 +51,7 @@ function useContentBlocks( blockTypes, block ) { }, [ blockTypes ] ); const isContentBlock = useCallback( ( blockName ) => { - return !! contenBlocksObjectAux[ blockName ]; + return !! contentBlocksObjectAux[ blockName ]; }, [ blockTypes ] ); @@ -166,28 +164,36 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { }; }, [] ); + const showTabs = window?.__experimentalEnableBlockInspectorTabs; + if ( count > 1 ) { return (
    - - - - - + { showTabs ? ( + + ) : ( + <> + + + + + + + ) }
    ); } @@ -229,6 +235,8 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { }; const BlockInspectorSingleBlock = ( { clientId, blockName } ) => { + const showTabs = window?.__experimentalEnableBlockInspectorTabs; + const hasBlockStyles = useSelect( ( select ) => { const { getBlockStyles } = select( blocksStore ); @@ -238,67 +246,64 @@ const BlockInspectorSingleBlock = ( { clientId, blockName } ) => { [ blockName ] ); const blockInformation = useBlockDisplayInformation( clientId ); + return (
    - { hasBlockStyles && ( -
    - - - { hasBlockSupport( - blockName, - 'defaultStylePicker', - true - ) && } - -
    + { showTabs && ( + + ) } + { ! showTabs && ( + <> + { hasBlockStyles && ( +
    + + + { hasBlockSupport( + blockName, + 'defaultStylePicker', + true + ) && ( + + ) } + +
    + ) } + + + + + +
    + +
    + ) } - - - - - -
    - -
    ); }; -const AdvancedControls = () => { - const fills = useSlotFills( InspectorAdvancedControls.slotName ); - const hasFills = Boolean( fills && fills.length ); - - if ( ! hasFills ) { - return null; - } - - return ( - - - - ); -}; - /** * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-inspector/README.md */ diff --git a/packages/block-editor/src/components/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss index b7cfdcf46333a..08ca013629927 100644 --- a/packages/block-editor/src/components/block-inspector/style.scss +++ b/packages/block-editor/src/components/block-inspector/style.scss @@ -27,7 +27,8 @@ } } -.block-editor-block-inspector__no-blocks { +.block-editor-block-inspector__no-blocks, +.block-editor-block-inspector__no-block-tools { display: block; font-size: $default-font-size; background: $white; @@ -35,6 +36,13 @@ text-align: center; } +.block-editor-block-inspector__no-block-tools { + border-top: $border-width solid $gray-300; +} + +.block-editor-block-inspector__tab-item { + flex: 1 1 0px; +} .block-editor-block-inspector__block-buttons-container { border-top: $border-width solid $gray-200; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js b/packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js new file mode 100644 index 0000000000000..83027861e9d19 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/advanced-controls-panel.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { + PanelBody, + __experimentalUseSlotFills as useSlotFills, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { + default as InspectorControls, + InspectorAdvancedControls, +} from '../inspector-controls'; + +const AdvancedControls = () => { + const fills = useSlotFills( InspectorAdvancedControls.slotName ); + const hasFills = Boolean( fills && fills.length ); + + if ( ! hasFills ) { + return null; + } + + return ( + + + + ); +}; + +export default AdvancedControls; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js new file mode 100644 index 0000000000000..deb3bccffaa16 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/appearance-tab.js @@ -0,0 +1,75 @@ +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; +import { + PanelBody, + __experimentalUseSlotFills as useSlotFills, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import BlockStyles from '../block-styles'; +import DefaultStylePicker from '../default-style-picker'; +import InspectorControls from '../inspector-controls'; +import InspectorControlsGroups from '../inspector-controls/groups'; + +const AppearanceTab = ( { + blockName, + clientId, + hasBlockStyles, + hasSingleBlockSelection = false, +} ) => { + const { border, color, dimensions, typography } = InspectorControlsGroups; + const appearanceFills = [ + ...( useSlotFills( border.Slot.__unstableName ) || [] ), + ...( useSlotFills( color.Slot.__unstableName ) || [] ), + ...( useSlotFills( dimensions.Slot.__unstableName ) || [] ), + ...( useSlotFills( typography.Slot.__unstableName ) || [] ), + ]; + + return ( + <> + { ! appearanceFills.length && ( + + { hasSingleBlockSelection + ? __( 'This block has no style options.' ) + : __( 'The selected blocks have no style options.' ) } + + ) } + { hasBlockStyles && ( +
    + + + { hasBlockSupport( + blockName, + 'defaultStylePicker', + true + ) && } + +
    + ) } + + + + + + ); +}; + +export default AppearanceTab; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/index.js b/packages/block-editor/src/components/inspector-controls-tabs/index.js new file mode 100644 index 0000000000000..62b87dd58e11c --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/index.js @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +import { TabPanel } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { TAB_SETTINGS, TAB_APPEARANCE } from './utils'; +import AppearanceTab from './appearance-tab'; +import SettingsTab from './settings-tab'; + +const tabs = [ TAB_APPEARANCE, TAB_SETTINGS ]; + +export default function InspectorControlsTabs( { + blockName, + clientId, + hasBlockStyles, +} ) { + return ( + + { ( tab ) => { + if ( tab.name === TAB_SETTINGS.name ) { + return ( + + ); + } + + if ( tab.name === TAB_APPEARANCE.name ) { + return ( + + ); + } + } } + + ); +} diff --git a/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js new file mode 100644 index 0000000000000..a000173807c19 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/settings-tab.js @@ -0,0 +1,43 @@ +/** + * WordPress dependencies + */ +import { __experimentalUseSlotFills as useSlotFills } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import AdvancedControls from './advanced-controls-panel'; +import InspectorControlsGroups from '../inspector-controls/groups'; +import { + default as InspectorControls, + InspectorAdvancedControls, +} from '../inspector-controls'; + +const SettingsTab = ( { hasSingleBlockSelection = false } ) => { + const { default: defaultGroup } = InspectorControlsGroups; + const settingsFills = [ + ...( useSlotFills( defaultGroup.Slot.__unstableName ) || [] ), + ...( useSlotFills( InspectorAdvancedControls.slotName ) || [] ), + ]; + + return ( + <> + + { hasSingleBlockSelection && ( +
    + +
    + ) } + { ! settingsFills.length && ( + + { hasSingleBlockSelection + ? __( 'This block has no settings.' ) + : __( 'The selected blocks have no settings.' ) } + + ) } + + ); +}; + +export default SettingsTab; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/utils.js b/packages/block-editor/src/components/inspector-controls-tabs/utils.js new file mode 100644 index 0000000000000..0bec1088174d3 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/utils.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { cog, styles } from '@wordpress/icons'; + +export const TAB_SETTINGS = { + name: 'settings', + title: 'Settings', + value: 'settings', + icon: cog, + className: 'block-editor-block-inspector__tab-item', +}; + +export const TAB_APPEARANCE = { + name: 'appearance', + title: 'Appearance', + value: 'appearance', + icon: styles, + className: 'block-editor-block-inspector__tab-item', +}; diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 62169314631bd..7f740706518c2 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancements + +- `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). + ## 22.1.0 (2022-11-16) ### Enhancements diff --git a/packages/components/src/tab-panel/README.md b/packages/components/src/tab-panel/README.md index a4a1c0c525370..c3e889a2bbf33 100644 --- a/packages/components/src/tab-panel/README.md +++ b/packages/components/src/tab-panel/README.md @@ -120,6 +120,7 @@ An array of objects containing the following properties: - `name`: `(string)` Defines the key for the tab. - `title`:`(string)` Defines the translated text for the tab. - `className`:`(string)` Optional. Defines the class to put on the tab. +- `icon`:`(ReactNode)` Optional. When set, displays the icon in place of the tab title. The title is then rendered as an aria-label and tooltip. > > **Note:** Other fields may be added to the object and accessed from the child function if desired. diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx index 206c2b8aab0a7..dfd3ebd945519 100644 --- a/packages/components/src/tab-panel/index.tsx +++ b/packages/components/src/tab-panel/index.tsx @@ -127,8 +127,11 @@ export function TabPanel( { selected={ tab.name === selected } key={ tab.name } onClick={ () => handleTabSelection( tab.name ) } + label={ tab.icon && tab.title } + icon={ tab.icon } + showTooltip={ !! tab.icon } > - { tab.title } + { ! tab.icon && tab.title } ) ) } diff --git a/packages/components/src/tab-panel/types.ts b/packages/components/src/tab-panel/types.ts index 2a89da6a215b7..1436b8034a66a 100644 --- a/packages/components/src/tab-panel/types.ts +++ b/packages/components/src/tab-panel/types.ts @@ -3,6 +3,11 @@ */ import type { ReactNode } from 'react'; +/** + * Internal dependencies + */ +import type { IconType } from '../icon'; + type Tab = { /** * The key of the tab. @@ -18,11 +23,14 @@ type Tab = { className?: string; } & Record< any, any >; -export type TabButtonProps = { +export type TabButtonProps< IconProps = unknown > = { children: ReactNode; className?: string; + icon?: IconType< IconProps >; + label?: string; onClick: ( event: MouseEvent ) => void; selected: boolean; + showTooltip?: boolean; tabId: string; }; diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index c044d0b49714c..677161c6b5a38 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -5,10 +5,12 @@ import { BlockInspector, store as blockEditorStore, } from '@wordpress/block-editor'; -import { cog } from '@wordpress/icons'; +import { useSelect } from '@wordpress/data'; import { Platform } from '@wordpress/element'; -import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { isRTL, __ } from '@wordpress/i18n'; +import { drawerLeft, drawerRight } from '@wordpress/icons'; import { store as interfaceStore } from '@wordpress/interface'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; /** * Internal dependencies @@ -25,8 +27,6 @@ import MetaBoxes from '../../meta-boxes'; import PluginDocumentSettingPanel from '../plugin-document-setting-panel'; import PluginSidebarEditPost from '../plugin-sidebar'; import TemplateSummary from '../template-summary'; -import { __ } from '@wordpress/i18n'; -import { useSelect } from '@wordpress/data'; import { store as editPostStore } from '../../../store'; const SIDEBAR_ACTIVE_BY_DEFAULT = Platform.select( { @@ -78,7 +78,7 @@ const SettingsSidebar = () => { /* translators: button label text should, if possible, be under 16 characters. */ title={ __( 'Settings' ) } toggleShortcut={ keyboardShortcut } - icon={ cog } + icon={ isRTL() ? drawerLeft : drawerRight } isActiveByDefault={ SIDEBAR_ACTIVE_BY_DEFAULT } > { ! isTemplateMode && sidebarName === 'edit-post/document' && ( diff --git a/packages/edit-site/src/components/sidebar-edit-mode/index.js b/packages/edit-site/src/components/sidebar-edit-mode/index.js index 080be432eb09d..12073a77b56e0 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/index.js @@ -2,8 +2,8 @@ * WordPress dependencies */ import { createSlotFill, PanelBody } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { cog } from '@wordpress/icons'; +import { isRTL, __ } from '@wordpress/i18n'; +import { drawerLeft, drawerRight } from '@wordpress/icons'; import { useEffect, Fragment } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as interfaceStore } from '@wordpress/interface'; @@ -78,7 +78,7 @@ export function SidebarComplementaryAreaFills() { } headerClassName="edit-site-sidebar-edit-mode__panel-tabs" diff --git a/packages/icons/src/index.js b/packages/icons/src/index.js index c7f891a5292db..5870ccba9ab04 100644 --- a/packages/icons/src/index.js +++ b/packages/icons/src/index.js @@ -66,6 +66,8 @@ export { default as currencyPound } from './library/currency-pound'; export { default as customPostType } from './library/custom-post-type'; export { default as desktop } from './library/desktop'; export { default as dragHandle } from './library/drag-handle'; +export { default as drawerLeft } from './library/drawer-left'; +export { default as drawerRight } from './library/drawer-right'; export { default as download } from './library/download'; export { default as edit } from './library/edit'; export { default as external } from './library/external'; diff --git a/packages/icons/src/library/drawer-left.js b/packages/icons/src/library/drawer-left.js new file mode 100644 index 0000000000000..2e1626fb1fe17 --- /dev/null +++ b/packages/icons/src/library/drawer-left.js @@ -0,0 +1,21 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const drawerLeft = ( + + + +); + +export default drawerLeft; diff --git a/packages/icons/src/library/drawer-right.js b/packages/icons/src/library/drawer-right.js new file mode 100644 index 0000000000000..95a8e72f775fb --- /dev/null +++ b/packages/icons/src/library/drawer-right.js @@ -0,0 +1,21 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/primitives'; + +const drawerRight = ( + + + +); + +export default drawerRight; From 7147465adf207a3aa6717a882d504d33934702a9 Mon Sep 17 00:00:00 2001 From: Corey Worrell Date: Thu, 17 Nov 2022 00:17:54 -0800 Subject: [PATCH 037/380] Fix for navigation anchor links to close modal (#45829) * Fix for navigation anchor links to close modal * Rename function to be more accurate --- packages/block-library/src/navigation/view-modal.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation/view-modal.js b/packages/block-library/src/navigation/view-modal.js index 477f05b5d5e51..9477d262816d9 100644 --- a/packages/block-library/src/navigation/view-modal.js +++ b/packages/block-library/src/navigation/view-modal.js @@ -27,6 +27,16 @@ function navigationToggleModal( modal ) { htmlElement.classList.toggle( 'has-modal-open' ); } +function isLinkToAnchorOnCurrentPage( node ) { + return ( + node.hash && + node.protocol === window.location.protocol && + node.host === window.location.host && + node.pathname === window.location.pathname && + node.search === window.location.search + ); +} + window.addEventListener( 'load', () => { MicroModal.init( { onShow: navigationToggleModal, @@ -42,7 +52,7 @@ window.addEventListener( 'load', () => { navigationLinks.forEach( function ( link ) { // Ignore non-anchor links and anchor links which open on a new tab. if ( - ! link.getAttribute( 'href' )?.startsWith( '#' ) || + ! isLinkToAnchorOnCurrentPage( link ) || link.attributes?.target === '_blank' ) { return; From 7ec665c6046ef6fbe326a3f71e872b2b332b32f5 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Thu, 17 Nov 2022 11:40:04 +0100 Subject: [PATCH 038/380] LinkControl unit tests: use user.type to type into search field (#45802) --- .../src/components/link-control/test/index.js | 88 +++++++------------ 1 file changed, 31 insertions(+), 57 deletions(-) diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index f5afb7e144f13..e7a65a0743a19 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -186,8 +186,7 @@ describe( 'Basic rendering', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); expect( screen.queryByText( '://' ) ).not.toBeInTheDocument(); } ); @@ -328,8 +327,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -364,8 +362,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -406,8 +403,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -447,8 +443,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( 'anything' ); + await user.type( searchInput, 'anything' ); const searchResultsField = screen.queryByRole( 'listbox' ); @@ -472,8 +467,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -510,8 +504,7 @@ describe( 'Searching for a link', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( 'couldbeurlorentitysearchterm' ); + await user.type( searchInput, 'couldbeurlorentitysearchterm' ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -544,8 +537,7 @@ describe( 'Manual link entry', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -591,13 +583,12 @@ describe( 'Manual link entry', () => { expect( submitButton ).toBeDisabled(); expect( submitButton ).toBeVisible(); - searchInput.focus(); if ( searchString.length ) { // Simulate searching for a term. - await user.keyboard( searchString ); + await user.type( searchInput, searchString ); } else { // Simulate clearing the search term. - await userEvent.clear( searchInput ); + await user.clear( searchInput ); } // fetchFauxEntitySuggestions resolves on next "tick" of event loop. @@ -637,13 +628,12 @@ describe( 'Manual link entry', () => { expect( submitButton ).toBeVisible(); // Simulate searching for a term. - searchInput.focus(); if ( searchString.length ) { // Simulate searching for a term. - await user.keyboard( searchString ); + await user.type( searchInput, searchString ); } else { // Simulate clearing the search term. - await userEvent.clear( searchInput ); + await user.clear( searchInput ); } // fetchFauxEntitySuggestions resolves on next "tick" of event loop. @@ -681,8 +671,7 @@ describe( 'Manual link entry', () => { } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -796,8 +785,7 @@ describe( 'Default search suggestions', () => { searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -911,8 +899,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( entityNameText ); + await user.type( searchInput, entityNameText ); await eventLoopTick(); @@ -983,8 +970,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( 'Some new page to create' ); + await user.type( searchInput, 'Some new page to create' ); await eventLoopTick(); @@ -1039,8 +1025,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( entityNameText ); + await user.type( searchInput, entityNameText ); await eventLoopTick(); @@ -1049,18 +1034,16 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { name: /Search results for.*/, } ) ).getAllByRole( 'option' ); - const createButton = Array.from( searchResultElements ).filter( - ( result ) => result.innerHTML.includes( 'Create:' ) - )[ 0 ]; + const createButton = searchResultElements.find( ( result ) => + result.innerHTML.includes( 'Create:' ) + ); // Step down into the search results, highlighting the first result item. triggerArrowDown( searchInput ); - createButton.focus(); - await user.keyboard( '[Enter]' ); + await user.click( createButton ); - searchInput.focus(); - await user.keyboard( '[Enter]' ); + await user.type( searchInput, '[Enter]' ); await eventLoopTick(); @@ -1088,8 +1071,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( entityNameText ); + await user.type( searchInput, entityNameText ); await eventLoopTick(); @@ -1167,8 +1149,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( inputText ); + await user.type( searchInput, inputText ); await eventLoopTick(); @@ -1205,8 +1186,7 @@ describe( 'Creating Entities (eg: Posts, Pages)', () => { searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchText ); + await user.type( searchInput, searchText ); await eventLoopTick(); @@ -1340,8 +1320,7 @@ describe( 'Selecting links', () => { } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -1406,8 +1385,7 @@ describe( 'Selecting links', () => { } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -1626,8 +1604,7 @@ describe( 'Post types', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -1657,8 +1634,7 @@ describe( 'Post types', () => { const searchInput = screen.getByRole( 'combobox', { name: 'URL' } ); // Simulate searching for a term. - searchInput.focus(); - await user.keyboard( searchTerm ); + await user.type( searchInput, searchTerm ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop. await eventLoopTick(); @@ -2050,8 +2026,7 @@ describe( 'Controlling link title text', () => { const textInput = screen.queryByRole( 'textbox', { name: 'Text' } ); - textInput.focus(); - await userEvent.clear( textInput ); + await user.clear( textInput ); await user.keyboard( textValue ); expect( textInput ).toHaveValue( textValue ); @@ -2087,8 +2062,7 @@ describe( 'Controlling link title text', () => { expect( textInput ).toBeVisible(); - textInput.focus(); - await userEvent.clear( textInput ); + await user.clear( textInput ); await user.keyboard( textValue ); // Attempt to submit the empty search value in the input. From dd166c113d38d28b372edf5b97f8143cc22cd04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Thu, 17 Nov 2022 04:11:34 -0700 Subject: [PATCH 039/380] Update `wp_theme_has_theme_json` to use `WP_Object_Cache` (#45543) Co-authored-by: Felix Arntz Co-authored-by: Miguel Torres Co-authored-by: Jonny Harris --- lib/compat/wordpress-6.2/default-filters.php | 11 +--- .../get-global-styles-and-settings.php | 66 ++++++++++++++----- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php index ac1ce19b42551..860cf9a8bf412 100644 --- a/lib/compat/wordpress-6.2/default-filters.php +++ b/lib/compat/wordpress-6.2/default-filters.php @@ -17,11 +17,6 @@ * @package gutenberg */ -/** - * Note for backport: we should also remove the existing filters: - * - * > add_action( 'switch_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) ); - * > add_action( 'start_previewing_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) ); - */ -add_action( 'switch_theme', 'wp_theme_clean_theme_json_cached_data' ); -add_action( 'start_previewing_theme', 'wp_theme_clean_theme_json_cached_data' ); +add_action( 'switch_theme', 'wp_theme_has_theme_json_clean_cache' ); +add_action( 'start_previewing_theme', 'wp_theme_has_theme_json_clean_cache' ); +add_action( 'upgrader_process_complete', '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme' ); diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index 4dbd9d0ba8bde..b2637969fd8ef 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -9,39 +9,69 @@ /** * Whether a theme or its parent have a theme.json file. * - * @param boolean $clear_cache Whether the cache should be cleared and theme support recomputed. Default is false. + * The result would be cached via the WP_Object_Cache. + * It can be cleared by calling wp_theme_has_theme_json_clean_cache(). * * @return boolean */ - function wp_theme_has_theme_json( $clear_cache = false ) { - static $theme_has_support = null; + function wp_theme_has_theme_json() { + $cache_group = 'theme_json'; + $cache_key = 'wp_theme_has_theme_json'; + $theme_has_support = wp_cache_get( $cache_key, $cache_group ); - if ( true === $clear_cache ) { - $theme_has_support = null; - } - - if ( null !== $theme_has_support ) { - return $theme_has_support; + /** + * $theme_has_support is stored as a int in the cache. + * + * The reason not to store it as a boolean is to avoid working + * with the $found parameter which apparently had some issues in some implementations + * https://developer.wordpress.org/reference/functions/wp_cache_get/ + */ + if ( 0 === $theme_has_support || 1 === $theme_has_support ) { + return (bool) $theme_has_support; } // Has the own theme a theme.json? - $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ); + $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ) ? 1 : 0; // Look up the parent if the child does not have a theme.json. - if ( ! $theme_has_support ) { - $theme_has_support = is_readable( get_template_directory() . '/theme.json' ); + if ( 0 === $theme_has_support ) { + $theme_has_support = is_readable( get_template_directory() . '/theme.json' ) ? 1 : 0; } - return $theme_has_support; + wp_cache_set( $cache_key, $theme_has_support, $cache_group ); + + return (bool) $theme_has_support; + } +} + +if ( ! function_exists( 'wp_theme_has_theme_json_clean_cache' ) ) { + /** + * Function to clean the cache used by wp_theme_has_theme_json method. + */ + function wp_theme_has_theme_json_clean_cache() { + wp_cache_delete( 'wp_theme_has_theme_json', 'theme_json' ); } } -if ( ! function_exists( 'wp_theme_clean_theme_json_cached_data' ) ) { +if ( ! function_exists( '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme' ) ) { /** - * Clean theme.json related cached data. + * Private function to clean the cache used by wp_theme_has_theme_json method. + * + * It is hooked into the `upgrader_process_complete` action. + * + * @see default-filters.php + * + * @param WP_Upgrader $upgrader Instance of WP_Upgrader class. + * @param array $options Metadata that identifies the data that is updated. */ - function wp_theme_clean_theme_json_cached_data() { - wp_theme_has_theme_json( true ); - WP_Theme_JSON_Resolver_Gutenberg::clean_cached_data(); + function _wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme( $upgrader, $options ) { + // The cache only needs cleaning when the active theme was updated. + if ( + 'update' === $options['action'] && + 'theme' === $options['type'] && + ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) + ) { + wp_theme_has_theme_json_clean_cache(); + } } } From 95c2b1311590517f279a261ef6eff6eab3bddfad Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 17 Nov 2022 11:46:45 +0000 Subject: [PATCH 040/380] Navigation: adds a warning about duplicate code for the future (#45844) --- packages/block-library/src/navigation-link/edit.js | 1 + packages/block-library/src/navigation-submenu/edit.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index ccd0aae57bcbb..ed0acced4efe8 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -714,6 +714,7 @@ export default function NavigationLinkEdit( { ) } + { /* Warning, this duplicated in packages/block-library/src/navigation-submenu/edit.js */ } + { /* Warning, this duplicated in packages/block-library/src/navigation-link/edit.js */ } Date: Thu, 17 Nov 2022 15:08:16 +0100 Subject: [PATCH 041/380] Small refactoring to the NavigableRegion component (#45849) --- .../src/components/interface-skeleton/index.js | 16 +++++++++------- .../src/components/navigable-region/index.js | 12 +++--------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/interface/src/components/interface-skeleton/index.js b/packages/interface/src/components/interface-skeleton/index.js index 560943aa5d963..f7d5ab36566e3 100644 --- a/packages/interface/src/components/interface-skeleton/index.js +++ b/packages/interface/src/components/interface-skeleton/index.js @@ -7,7 +7,10 @@ import classnames from 'classnames'; * WordPress dependencies */ import { forwardRef, useEffect } from '@wordpress/element'; -import { __unstableUseNavigateRegions as useNavigateRegions } from '@wordpress/components'; +import { + __unstableUseNavigateRegions as useNavigateRegions, + __unstableMotion as motion, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMergeRefs } from '@wordpress/compose'; @@ -101,14 +104,13 @@ function InterfaceSkeleton(
    { !! header && isDistractionFree && ( { header } diff --git a/packages/interface/src/components/navigable-region/index.js b/packages/interface/src/components/navigable-region/index.js index 3b86b856b1d3f..31037ffd33354 100644 --- a/packages/interface/src/components/navigable-region/index.js +++ b/packages/interface/src/components/navigable-region/index.js @@ -3,26 +3,20 @@ */ import classnames from 'classnames'; -/** - * WordPress dependencies - */ -import { __unstableMotion as motion } from '@wordpress/components'; - export default function NavigableRegion( { children, className, ariaLabel, - motionProps = {}, + as: Tag = 'div', + ...props } ) { - const Tag = Object.keys( motionProps ).length ? motion.div : 'div'; - return (
    { children } From 49d03013815da46014f4316f3b9e5434047b72cf Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Sat, 12 Nov 2022 15:57:28 -0700 Subject: [PATCH 042/380] Performance Test: Reuse tests-branch build if possible When running the performance test suite we currently build three copies of the plugin: one for each branch under test and also one for the branch which provides the actual test suite. In this patch we're checking first if one of the branches under test is the same as that tests branch, and if so, re-using the built files from that to avoid the additional approximately five minutes of building. --- bin/plugin/commands/performance.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index 7101dc6511808..b55618cbfd180 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -260,12 +260,23 @@ async function runPerformanceTests( branches, options ) { await runShellScript( 'mkdir ' + environmentDirectory ); await runShellScript( `cp -R ${ baseDirectory } ${ buildPath }` ); - log( ` >> Fetching the ${ formats.success( branch ) } branch` ); - // @ts-ignore - await SimpleGit( buildPath ).reset( 'hard' ).checkout( branch ); + const fancyBranch = formats.success( branch ); - log( ` >> Building the ${ formats.success( branch ) } branch` ); - await runShellScript( 'npm ci && npm run build', buildPath ); + if ( branch === options.testsBranch ) { + log( + ` >> Re-using the testing branch for ${ fancyBranch }` + ); + await runShellScript( + `cp -R ${ performanceTestDirectory } ${ buildPath }` + ); + } else { + log( ` >> Fetching the ${ fancyBranch } branch` ); + // @ts-ignore + await SimpleGit( buildPath ).reset( 'hard' ).checkout( branch ); + + log( ` >> Building the ${ fancyBranch } branch` ); + await runShellScript( 'npm ci && npm run build', buildPath ); + } await runShellScript( 'cp ' + From a71adfd1a23071fb707fd9cba27830c5c1e7c385 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Thu, 17 Nov 2022 17:08:42 +0000 Subject: [PATCH 043/380] List View - Stop child item selecting a parent which is already selected (#45860) --- .../block-settings-dropdown.js | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js index 87fc6bc5e0585..9ebc961440f7a 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js +++ b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js @@ -189,6 +189,11 @@ export function BlockSettingsDropdown( { }, } ); + // This can occur when the selected block (the parent) + // displays child blocks within a List View. + const parentBlockIsSelected = + selectedBlockClientIds?.includes( firstParentClientId ); + return ( - { !! firstParentClientId && ( - - } - onClick={ () => - selectBlock( firstParentClientId ) - } - > - { sprintf( - /* translators: %s: Name of the block's parent. */ - __( 'Select parent block (%s)' ), - parentBlockType.title - ) } - - ) } + { ! parentBlockIsSelected && + !! firstParentClientId && ( + + } + onClick={ () => + selectBlock( + firstParentClientId + ) + } + > + { sprintf( + /* translators: %s: Name of the block's parent. */ + __( + 'Select parent block (%s)' + ), + parentBlockType.title + ) } + + ) } { count === 1 && ( Date: Thu, 17 Nov 2022 17:16:16 +0000 Subject: [PATCH 044/380] Update Changelog for 14.5.3 --- changelog.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/changelog.txt b/changelog.txt index 548a18a5ef464..809e3f5ebc6bc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,30 @@ == Changelog == += 14.5.3 = + + + +## Changelog + +### Performance + +#### Global Styles +- Update `wp_theme_has_theme_json` to use `WP_Object_Cache`. ([45543](https://github.com/WordPress/gutenberg/pull/45543)) + + +## First time contributors + +The following PRs were merged by first time contributors: + + + +## Contributors + +The following contributors merged PRs in this release: + +@oandregal + + = 14.5.2 = From 8e83fffecc34fa919722a7bbcad93ce0571e7bb8 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Thu, 10 Nov 2022 17:52:43 -0700 Subject: [PATCH 045/380] Perf Tests: Stop building project types during build phase We don't use the types we build during the test runs, but generating them performs additional wasted work. Let's skip over that step to cut out unecessary delay and to avoid computation we don't need. --- bin/plugin/commands/performance.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index b55618cbfd180..a7f9c81e8cebb 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -241,7 +241,7 @@ async function runPerformanceTests( branches, options ) { log( ' >> Installing dependencies and building packages' ); await runShellScript( - 'npm ci && npm run build:packages', + 'npm ci && node ./bin/packages/build.js', performanceTestDirectory ); log( ' >> Creating the environment folders' ); @@ -275,7 +275,10 @@ async function runPerformanceTests( branches, options ) { await SimpleGit( buildPath ).reset( 'hard' ).checkout( branch ); log( ` >> Building the ${ fancyBranch } branch` ); - await runShellScript( 'npm ci && npm run build', buildPath ); + await runShellScript( + 'npm ci && node ./bin/packages/build.js', + buildPath + ); } await runShellScript( From b9da345fadf827e509025e8775614142cd10a392 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Fri, 18 Nov 2022 02:45:22 +0000 Subject: [PATCH 046/380] Navigation: Extract components (#45850) --- .../src/navigation/edit/index.js | 120 ++++++---------- .../edit/menu-inspector-controls.js | 135 ++++++++++++++++++ 2 files changed, 177 insertions(+), 78 deletions(-) create mode 100644 packages/block-library/src/navigation/edit/menu-inspector-controls.js diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 6cf5eea22e0a0..588f4397274bd 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -14,7 +14,6 @@ import { useMemo, } from '@wordpress/element'; import { - __experimentalOffCanvasEditor as OffCanvasEditor, InspectorControls, useBlockProps, __experimentalRecursionProvider as RecursionProvider, @@ -37,8 +36,6 @@ import { __experimentalToggleGroupControlOption as ToggleGroupControlOption, Button, Spinner, - __experimentalHStack as HStack, - __experimentalHeading as Heading, } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; @@ -53,7 +50,6 @@ import useNavigationEntities from '../use-navigation-entities'; import Placeholder from './placeholder'; import ResponsiveWrapper from './responsive-wrapper'; import NavigationInnerBlocks from './inner-blocks'; -import NavigationMenuSelector from './navigation-menu-selector'; import NavigationMenuNameControl from './navigation-menu-name-control'; import UnsavedInnerBlocks from './unsaved-inner-blocks'; import NavigationMenuDeleteControl from './navigation-menu-delete-control'; @@ -69,6 +65,7 @@ import useCreateNavigationMenu from './use-create-navigation-menu'; import { useInnerBlocks } from './use-inner-blocks'; import { detectColors } from './utils'; import ManageMenusButton from './manage-menus-button'; +import MenuInspectorControls from './menu-inspector-controls'; function Navigation( { attributes, @@ -663,84 +660,26 @@ function Navigation( { // that automatically saves the menu as an entity when changes are made to the inner blocks. const hasUnsavedBlocks = hasUncontrolledInnerBlocks && ! isEntityAvailable; - const WrappedNavigationMenuSelector = ( { currentMenuId } ) => ( - { - handleUpdateMenu( menuId ); - } } - onSelectClassicMenu={ async ( classicMenu ) => { - const navMenu = await convertClassicMenu( - classicMenu.id, - classicMenu.name, - 'draft' - ); - if ( navMenu ) { - handleUpdateMenu( navMenu.id, { - focusNavigationBlock: true, - } ); - } - } } - onCreateNew={ createUntitledEmptyNavigationMenu } - createNavigationMenuIsSuccess={ createNavigationMenuIsSuccess } - createNavigationMenuIsError={ createNavigationMenuIsError } - /* translators: %s: The name of a menu. */ - actionLabel={ __( "Switch to '%s'" ) } - /> - ); - const isManageMenusButtonDisabled = ! hasManagePermissions || ! hasResolvedNavigationMenus; - const MenuInspectorControls = ( { currentMenuId = null } ) => ( - - - { isOffCanvasNavigationEditorEnabled ? ( - <> - - - { __( 'Menu' ) } - - - - { currentMenuId && isNavigationMenuMissing ? ( -

    { __( 'Select or create a menu' ) }

    - ) : ( - - ) } - - ) : ( - <> - - - - ) } -
    -
    - ); - if ( hasUnsavedBlocks && ! isCreatingNavigationMenu ) { return ( - + { stylingInspectorControls } - + { __( 'Navigation menu has been deleted or is unavailable. ' @@ -845,7 +796,20 @@ function Navigation( { return ( - + { stylingInspectorControls } { isEntityAvailable && ( diff --git a/packages/block-library/src/navigation/edit/menu-inspector-controls.js b/packages/block-library/src/navigation/edit/menu-inspector-controls.js new file mode 100644 index 0000000000000..9f92d130ff14d --- /dev/null +++ b/packages/block-library/src/navigation/edit/menu-inspector-controls.js @@ -0,0 +1,135 @@ +/** + * WordPress dependencies + */ +import { + __experimentalOffCanvasEditor as OffCanvasEditor, + InspectorControls, +} from '@wordpress/block-editor'; +import { + PanelBody, + __experimentalHStack as HStack, + __experimentalHeading as Heading, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import ManageMenusButton from './manage-menus-button'; +import NavigationMenuSelector from './navigation-menu-selector'; + +const WrappedNavigationMenuSelector = ( { + clientId, + currentMenuId, + handleUpdateMenu, + convertClassicMenu, + onCreateNew, + createNavigationMenuIsSuccess, + createNavigationMenuIsError, +} ) => ( + { + handleUpdateMenu( menuId ); + } } + onSelectClassicMenu={ async ( classicMenu ) => { + const navMenu = await convertClassicMenu( + classicMenu.id, + classicMenu.name, + 'draft' + ); + if ( navMenu ) { + handleUpdateMenu( navMenu.id, { + focusNavigationBlock: true, + } ); + } + } } + onCreateNew={ onCreateNew } + createNavigationMenuIsSuccess={ createNavigationMenuIsSuccess } + createNavigationMenuIsError={ createNavigationMenuIsError } + /* translators: %s: The name of a menu. */ + actionLabel={ __( "Switch to '%s'" ) } + /> +); +const MenuInspectorControls = ( { + clientId, + convertClassicMenu, + createNavigationMenuIsSuccess, + createNavigationMenuIsError, + currentMenuId = null, + handleUpdateMenu, + isNavigationMenuMissing, + innerBlocks, + isManageMenusButtonDisabled, + onCreateNew, +} ) => { + const isOffCanvasNavigationEditorEnabled = + window?.__experimentalEnableOffCanvasNavigationEditor === true; + + return ( + + + { isOffCanvasNavigationEditorEnabled ? ( + <> + + + { __( 'Menu' ) } + + + + { currentMenuId && isNavigationMenuMissing ? ( +

    { __( 'Select or create a menu' ) }

    + ) : ( + + ) } + + ) : ( + <> + + + + ) } +
    +
    + ); +}; + +export default MenuInspectorControls; From 2a2ecb319ef40ca4988a0923b6cd22b2d60bf645 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Fri, 18 Nov 2022 17:23:54 +0900 Subject: [PATCH 047/380] Storybook: Add link to component folder on GitHub, retire Storysource (#45727) * Storybook: Add source link button * Remove storysource addon * Update docs * Add link to relevant plugin --- package-lock.json | 153 +++++------------- package.json | 2 +- storybook/main.js | 2 +- storybook/preview.js | 1 + storybook/stories/docs/inline-icon.js | 14 ++ storybook/stories/docs/introduction.story.mdx | 20 +-- storybook/stories/playground/index.js | 3 + storybook/webpack.config.js | 9 ++ storybook/webpack/source-link-loader.js | 67 ++++++++ 9 files changed, 146 insertions(+), 125 deletions(-) create mode 100644 storybook/stories/docs/inline-icon.js create mode 100644 storybook/webpack/source-link-loader.js diff --git a/package-lock.json b/package-lock.json index 9f0f34dd3c5bf..adb96b28da921 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9831,87 +9831,6 @@ } } }, - "@storybook/addon-storysource": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-6.5.7.tgz", - "integrity": "sha512-4cquq0Oqf9skPdIYkVtO5lAPS7+83TyY4vT52HQ5LSpSekZELQkRxN793jNMQdu2IGQ/l9WqvyzFGaN62bRg7Q==", - "dev": true, - "requires": { - "@storybook/addons": "6.5.7", - "@storybook/api": "6.5.7", - "@storybook/client-logger": "6.5.7", - "@storybook/components": "6.5.7", - "@storybook/router": "6.5.7", - "@storybook/source-loader": "6.5.7", - "@storybook/theming": "6.5.7", - "core-js": "^3.8.2", - "estraverse": "^5.2.0", - "loader-utils": "^2.0.0", - "prop-types": "^15.7.2", - "react-syntax-highlighter": "^15.4.5", - "regenerator-runtime": "^0.13.7" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dev": true, - "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, - "prismjs": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.28.0.tgz", - "integrity": "sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==", - "dev": true - }, - "react-syntax-highlighter": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz", - "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.3.1", - "highlight.js": "^10.4.1", - "lowlight": "^1.17.0", - "prismjs": "^1.27.0", - "refractor": "^3.6.0" - } - }, - "refractor": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", - "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", - "dev": true, - "requires": { - "hastscript": "^6.0.0", - "parse-entities": "^2.0.0", - "prismjs": "~1.27.0" - }, - "dependencies": { - "prismjs": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", - "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", - "dev": true - } - } - } - } - }, "@storybook/addon-toolbars": { "version": "6.5.7", "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-6.5.7.tgz", @@ -19608,7 +19527,7 @@ "app-root-dir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz", - "integrity": "sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg=", + "integrity": "sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==", "dev": true }, "app-root-path": { @@ -26253,7 +26172,7 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", "dev": true, "optional": true }, @@ -27890,7 +27809,7 @@ "babel-plugin-add-react-displayname": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", - "integrity": "sha1-M51M3be2X9YtHfnbn+BN4TQSK9U=", + "integrity": "sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw==", "dev": true }, "babel-plugin-apply-mdx-type-prop": { @@ -28313,7 +28232,7 @@ "batch-processor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz", - "integrity": "sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg=", + "integrity": "sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==", "dev": true }, "bcrypt-pbkdf": { @@ -29276,7 +29195,7 @@ "camelcase-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", "dev": true, "optional": true, "requires": { @@ -29287,7 +29206,7 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", "dev": true, "optional": true } @@ -31676,7 +31595,7 @@ "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", "dev": true, "optional": true, "requires": { @@ -36599,7 +36518,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", "dev": true, "optional": true }, @@ -36830,7 +36749,7 @@ "glob-to-regexp": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "integrity": "sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==", "dev": true }, "global": { @@ -37125,7 +37044,7 @@ "has-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-glob/-/has-glob-1.0.0.tgz", - "integrity": "sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc=", + "integrity": "sha512-D+8A457fBShSEI3tFCj65PAbT++5sKiFtdCdOam0gnfBgw9D277OERk+HM9qYJXmdVLZ/znez10SqHN0BBQ50g==", "dev": true, "requires": { "is-glob": "^3.0.0" @@ -37134,7 +37053,7 @@ "is-glob": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", "dev": true, "requires": { "is-extglob": "^2.1.0" @@ -38999,7 +38918,7 @@ "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", "dev": true, "optional": true }, @@ -39021,7 +38940,7 @@ "is-window": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz", - "integrity": "sha1-LIlspT25feRdPDMTOmXYyfVjSA0=", + "integrity": "sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg==", "dev": true }, "is-windows": { @@ -42398,7 +42317,7 @@ "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=", + "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", "dev": true }, "js-tokens": { @@ -43719,7 +43638,7 @@ "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", "dev": true, "optional": true, "requires": { @@ -43733,7 +43652,7 @@ "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", "dev": true, "optional": true, "requires": { @@ -44041,7 +43960,7 @@ "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", "dev": true, "optional": true, "requires": { @@ -44291,7 +44210,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "dev": true, "optional": true }, @@ -47331,7 +47250,7 @@ "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "integrity": "sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==", "dev": true }, "number-is-nan": { @@ -48809,7 +48728,7 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", "dev": true }, "p-event": { @@ -50383,7 +50302,7 @@ "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", "dev": true }, "prismjs": { @@ -52435,7 +52354,7 @@ "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "integrity": "sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==", "dev": true, "optional": true, "requires": { @@ -52446,7 +52365,7 @@ "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "integrity": "sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==", "dev": true, "optional": true, "requires": { @@ -52689,7 +52608,7 @@ "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", "dev": true }, "remark": { @@ -53227,7 +53146,7 @@ "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", "dev": true, "optional": true, "requires": { @@ -54021,7 +53940,7 @@ "serve-favicon": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/serve-favicon/-/serve-favicon-2.5.0.tgz", - "integrity": "sha1-k10kDN/g9YBTB/3+ln2IlCosvPA=", + "integrity": "sha512-FMW2RvqNr03x+C0WxTyu6sOv21oOjkq5j8tjquWccwa6ScNyGFOGJVpuS1NmTVGBAHS07xnSKotgf2ehQmf9iA==", "dev": true, "requires": { "etag": "~1.8.1", @@ -55362,6 +55281,12 @@ "integrity": "sha512-7t+/wpKLanLzSnQPX8WAcuLCCeuSHoWdQuh9SB3xD0kNOM38DNf+0Oa+wmvxmYueRzkmh6IcdKFtvTa+ecgPDw==", "dev": true }, + "storybook-source-link": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/storybook-source-link/-/storybook-source-link-2.0.3.tgz", + "integrity": "sha512-Vw/ECmTObbcGbS6mX2bolQUF6c/Z4iBtcJBQh6T/3uFDz8pmzGo840hfSJDKXwwatHWDZ1V70jDLCDb4fbXQYg==", + "dev": true + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -56174,7 +56099,7 @@ "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", "dev": true, "optional": true, "requires": { @@ -56195,7 +56120,7 @@ "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==", "dev": true, "optional": true, "requires": { @@ -57942,7 +57867,7 @@ "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "integrity": "sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==", "dev": true, "optional": true }, @@ -58455,7 +58380,7 @@ "untildify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", - "integrity": "sha1-F+soB5h/dpUunASF/DEdBqgmouA=", + "integrity": "sha512-sJjbDp2GodvkB0FZZcn7k6afVisqX5BZD7Yq3xp4nN2O15BBK0cLm3Vwn2vQaF7UDS0UUsrQMkkplmDI5fskig==", "dev": true, "optional": true, "requires": { @@ -58731,7 +58656,7 @@ "utila": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", "dev": true }, "utility-types": { @@ -58752,7 +58677,7 @@ "uuid-browser": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid-browser/-/uuid-browser-3.1.0.tgz", - "integrity": "sha1-DwWkCu90+eWVHiDvv0SxGHHlZBA=", + "integrity": "sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg==", "dev": true }, "v8-compile-cache": { @@ -60615,7 +60540,7 @@ "x-default-browser": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/x-default-browser/-/x-default-browser-0.4.0.tgz", - "integrity": "sha1-cM8NqF2nwKtcsPFaiX8jIqa91IE=", + "integrity": "sha512-7LKo7RtWfoFN/rHx1UELv/2zHGMx8MkZKDq1xENmOCTkfIqZJ0zZ26NEJX8czhnPXVcqS0ARjjfJB+eJ0/5Cvw==", "dev": true, "requires": { "default-browser-id": "^1.0.4" diff --git a/package.json b/package.json index 98f8d07607f67..0168806a72ec3 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,6 @@ "@storybook/addon-controls": "6.5.7", "@storybook/addon-docs": "6.5.7", "@storybook/addon-knobs": "6.2.9", - "@storybook/addon-storysource": "6.5.7", "@storybook/addon-toolbars": "6.5.7", "@storybook/addon-viewport": "6.5.7", "@storybook/builder-webpack5": "6.5.7", @@ -234,6 +233,7 @@ "snapshot-diff": "0.8.1", "source-map-loader": "3.0.0", "sprintf-js": "1.1.1", + "storybook-source-link": "2.0.3", "style-loader": "3.2.1", "terser-webpack-plugin": "5.1.4", "typescript": "4.4.2", diff --git a/storybook/main.js b/storybook/main.js index 99c8c2ea84bec..717f42f7fb5e6 100644 --- a/storybook/main.js +++ b/storybook/main.js @@ -19,11 +19,11 @@ module.exports = { }, '@storybook/addon-controls', '@storybook/addon-knobs', // Deprecated, new stories should use addon-controls. - '@storybook/addon-storysource', '@storybook/addon-viewport', '@storybook/addon-a11y', '@storybook/addon-toolbars', '@storybook/addon-actions', + 'storybook-source-link', ], features: { babelModeV7: true, diff --git a/storybook/preview.js b/storybook/preview.js index 16e8f71cf5346..c7c2463f797d7 100644 --- a/storybook/preview.js +++ b/storybook/preview.js @@ -107,4 +107,5 @@ export const parameters = { ], }, }, + sourceLinkPrefix: 'https://github.com/WordPress/gutenberg/blob/trunk/', }; diff --git a/storybook/stories/docs/inline-icon.js b/storybook/stories/docs/inline-icon.js new file mode 100644 index 0000000000000..2a1056b0069b7 --- /dev/null +++ b/storybook/stories/docs/inline-icon.js @@ -0,0 +1,14 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; +import { Icons } from '@storybook/components'; + +const StyledIcons = styled( Icons )` + display: inline-block !important; + width: 14px; +`; + +export const InlineIcon = ( props ) => ( + +); diff --git a/storybook/stories/docs/introduction.story.mdx b/storybook/stories/docs/introduction.story.mdx index bdad5c0336ebf..f353cf7c7e0ab 100644 --- a/storybook/stories/docs/introduction.story.mdx +++ b/storybook/stories/docs/introduction.story.mdx @@ -1,4 +1,5 @@ import { Meta } from '@storybook/addon-docs'; +import { InlineIcon } from './inline-icon'; @@ -17,24 +18,25 @@ Import them from the components root directory like in below example: import { Button } from '@wordpress/components'; export default function MyButton() { - return ; + return ; } -```` +``` ## How this site works -The site shows the individual components in the sidebar and the Canvas on the right. Select the component you’d like to explore, and you’ll see the display on the Canvas tab. If the component also has controls/arguments, you can modify them on the Controls tab on the lower half of the screen. +The site shows the individual components in the sidebar and the Canvas on the right. Select the component you’d like to explore, and you’ll see the display on the **Canvas** tab. If the component also has controls/arguments, you can modify them on the **Controls** tab on the lower half of the screen. To view the documentation for each component use the **Docs** menu item in the top toolbar. +To view the source code for the component and its stories on GitHub, click the View Source Repository button in the top right corner. + To use it in your local development environment run the following command in the top level Gutenberg directory: - ```bash - npm run storybook:dev - ``` +```bash +npm run storybook:dev +``` ## Resources to learn more: -- [Storybook.js.org](https://storybook.js.org/) - Storybook is a frontend workshop for building UI components and pages in isolation. -- [[Package] Components](https://github.com/WordPress/gutenberg/issues?q=is%3Aopen+is%3Aissue+label%3A%22%5BPackage%5D+Components%22) - Open Issue Gutenberg Repo -- [On the known "loading source..." issue](https://github.com/WordPress/gutenberg/issues/45095) at the 'Story' tab for some components +- [Storybook.js.org](https://storybook.js.org/) - Storybook is a frontend workshop for building UI components and pages in isolation. +- [[Package] Components](https://github.com/WordPress/gutenberg/issues?q=is%3Aopen+is%3Aissue+label%3A%22%5BPackage%5D+Components%22) - Open Issue Gutenberg Repo diff --git a/storybook/stories/playground/index.js b/storybook/stories/playground/index.js index a13001f96f28b..92ccd78ae58b7 100644 --- a/storybook/stories/playground/index.js +++ b/storybook/stories/playground/index.js @@ -70,6 +70,9 @@ function App() { export default { title: 'Playground/Block Editor', + parameters: { + sourceLink: 'storybook/stories/playground', + }, }; export const _default = () => { diff --git a/storybook/webpack.config.js b/storybook/webpack.config.js index 000c6930574f9..1747cc2f9caa0 100644 --- a/storybook/webpack.config.js +++ b/storybook/webpack.config.js @@ -44,6 +44,15 @@ module.exports = ( { config } ) => { ), enforce: 'post', }, + { + // Adds a `sourceLink` parameter to the story metadata, based on the file path + test: /\/stories\/.+\.(j|t)sx?$/, + loader: path.resolve( + __dirname, + './webpack/source-link-loader.js' + ), + enforce: 'post', + }, { test: /\.scss$/, exclude: /\.lazy\.scss$/, diff --git a/storybook/webpack/source-link-loader.js b/storybook/webpack/source-link-loader.js new file mode 100644 index 0000000000000..d9501aa737dad --- /dev/null +++ b/storybook/webpack/source-link-loader.js @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +const babel = require( '@babel/core' ); +const path = require( 'path' ); + +const REPO_ROOT = path.resolve( __dirname, '../../' ); + +/** + * Adds a `sourceLink` parameter to the story metadata, based on the file path. + * + * @see https://storybook.js.org/addons/storybook-source-link + */ +function addSourceLinkPlugin() { + return { + visitor: { + ExportDefaultDeclaration( visitorPath, state ) { + const componentPath = getComponentPathFromStoryPath( + state.file.opts.filename + ); + const properties = + // When default export is anonymous, the declaration is an object expression + visitorPath.node.declaration.properties ?? + // When default export is named, the declaration is an identifier, usually the previous node + visitorPath.getPrevSibling().node.declarations[ 0 ].init + .properties; + + alterParameters( properties, componentPath ); + }, + }, + }; +} + +function getComponentPathFromStoryPath( storyPath ) { + const componentRoot = path.resolve( storyPath, '../../' ); + return path.relative( REPO_ROOT, componentRoot ); +} + +function alterParameters( properties, componentPath ) { + const sourceLink = babel.types.objectProperty( + babel.types.identifier( 'sourceLink' ), + babel.types.stringLiteral( componentPath ) + ); + + let parameters = properties.find( ( op ) => op.key.name === 'parameters' ); + + if ( ! parameters ) { + parameters = babel.types.objectProperty( + babel.types.identifier( 'parameters' ) + ); + properties.push( parameters ); + } + + parameters.value.properties = [ + sourceLink, + ...parameters.value.properties, + ]; +} + +module.exports = function ( source, { sources } ) { + const output = babel.transform( source, { + plugins: [ addSourceLinkPlugin ], + filename: sources[ 0 ], + sourceType: 'module', + } ); + return output.code; +}; From a5f2b81fa25983dcd23f13045f2b7f62cdce1b7f Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 18 Nov 2022 20:26:44 +1000 Subject: [PATCH 048/380] Sidebar: Add list view tab for Navigation block et al. (#45483) * Add drawer icons to icons package Switch editor sidebar icons to new drawer icons Icon should reflect RTL as well. Update TabPanel to allow icons on TabButtons Add menu group to InspectorControl slot fills Move nav menu controls into InspectorControls menu group Introduce menu, settings & appearance tabs to sidebar Refactor InspectorControlTabs Re-orders the appearance and settings tabs. Also now omits the TabPanel altogether if only a single tab has contents. Make block inspector tabs a Gutenberg experiment A little tidy up Clean up conditional tabs display Remove nav specific menu tab for now Remove Menu inspector controls group Refactor inspector controls tabs to components Remove conditional display of tabs Render no settings or tools messages when no fills Reduce new styles for equal width tabs Add general slot for items applying to block as a whole Move query block new post link to new slot Revert "Move query block new post link to new slot" This reverts commit 12799852648a67cb566112639d4d71e33a2d9834. Revert "Add general slot for items applying to block as a whole" This reverts commit 186276fb42af1a7dd571892b349eaacd34f0a3b0. Tweak no style options message Add changelog for TabPanel updates Remove empty readme until experiment settles Fix copy and paste error Provide list view tab and slot for nav block * change white to allow Co-authored-by: Ben Dwyer --- .../inspector-controls-tabs/index.js | 19 +++++++- .../inspector-controls-tabs/list-view-tab.js | 44 +++++++++++++++++++ .../inspector-controls-tabs/utils.js | 10 ++++- .../components/inspector-controls/groups.js | 2 + .../edit/menu-inspector-controls.js | 5 ++- packages/components/CHANGELOG.md | 1 + 6 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 packages/block-editor/src/components/inspector-controls-tabs/list-view-tab.js diff --git a/packages/block-editor/src/components/inspector-controls-tabs/index.js b/packages/block-editor/src/components/inspector-controls-tabs/index.js index 62b87dd58e11c..ad34f5625b5dd 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/index.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/index.js @@ -6,17 +6,23 @@ import { TabPanel } from '@wordpress/components'; /** * Internal dependencies */ -import { TAB_SETTINGS, TAB_APPEARANCE } from './utils'; +import { TAB_SETTINGS, TAB_APPEARANCE, TAB_LIST_VIEW } from './utils'; import AppearanceTab from './appearance-tab'; import SettingsTab from './settings-tab'; +import { default as ListViewTab, useIsListViewDisabled } from './list-view-tab'; -const tabs = [ TAB_APPEARANCE, TAB_SETTINGS ]; +const defaultTabs = [ TAB_APPEARANCE, TAB_SETTINGS ]; +const tabsWithListView = [ TAB_LIST_VIEW, TAB_APPEARANCE, TAB_SETTINGS ]; export default function InspectorControlsTabs( { blockName, clientId, hasBlockStyles, } ) { + const tabs = useIsListViewDisabled( blockName ) + ? defaultTabs + : tabsWithListView; + return ( { ( tab ) => { @@ -36,6 +42,15 @@ export default function InspectorControlsTabs( { /> ); } + + if ( tab.name === TAB_LIST_VIEW.name ) { + return ( + + ); + } } } ); diff --git a/packages/block-editor/src/components/inspector-controls-tabs/list-view-tab.js b/packages/block-editor/src/components/inspector-controls-tabs/list-view-tab.js new file mode 100644 index 0000000000000..d95f39b6e9651 --- /dev/null +++ b/packages/block-editor/src/components/inspector-controls-tabs/list-view-tab.js @@ -0,0 +1,44 @@ +/** + * WordPress dependencies + */ +import { __experimentalUseSlotFills as useSlotFills } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import InspectorControls from '../inspector-controls'; +import InspectorControlsGroups from '../inspector-controls/groups'; + +// This tab restricts the blocks that may render to it via the allowlist below. +const allowlist = [ 'core/navigation' ]; + +export const useIsListViewDisabled = ( blockName ) => { + return ! allowlist.includes( blockName ); +}; + +const ListViewTab = ( { blockName, hasSingleBlockSelection } ) => { + const { list } = InspectorControlsGroups; + const fills = useSlotFills( list.Slot.__unstableName ) || []; + + // Unlike other tabs the List View is much more niche. As such it will be + // omitted if the current block isn't in the allowlist. + if ( useIsListViewDisabled( blockName ) ) { + return; + } + + return ( + <> + { ! fills.length && ( + + { hasSingleBlockSelection + ? __( 'This block has no list options.' ) + : __( 'The selected blocks have no list options.' ) } + + ) } + + + ); +}; + +export default ListViewTab; diff --git a/packages/block-editor/src/components/inspector-controls-tabs/utils.js b/packages/block-editor/src/components/inspector-controls-tabs/utils.js index 0bec1088174d3..4ab6f0164a19c 100644 --- a/packages/block-editor/src/components/inspector-controls-tabs/utils.js +++ b/packages/block-editor/src/components/inspector-controls-tabs/utils.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { cog, styles } from '@wordpress/icons'; +import { cog, styles, listView } from '@wordpress/icons'; export const TAB_SETTINGS = { name: 'settings', @@ -18,3 +18,11 @@ export const TAB_APPEARANCE = { icon: styles, className: 'block-editor-block-inspector__tab-item', }; + +export const TAB_LIST_VIEW = { + name: 'list', + title: 'List View', + value: 'list-view', + icon: listView, + className: 'block-editor-block-inspector__tab-item', +}; diff --git a/packages/block-editor/src/components/inspector-controls/groups.js b/packages/block-editor/src/components/inspector-controls/groups.js index 0b7d1d2f4479f..cb03c1ff13fa5 100644 --- a/packages/block-editor/src/components/inspector-controls/groups.js +++ b/packages/block-editor/src/components/inspector-controls/groups.js @@ -13,6 +13,7 @@ const InspectorControlsDimensions = createSlotFill( const InspectorControlsTypography = createSlotFill( 'InspectorControlsTypography' ); +const InspectorControlsListView = createSlotFill( 'InspectorControlsListView' ); const groups = { default: InspectorControlsDefault, @@ -20,6 +21,7 @@ const groups = { border: InspectorControlsBorder, color: InspectorControlsColor, dimensions: InspectorControlsDimensions, + list: InspectorControlsListView, typography: InspectorControlsTypography, }; diff --git a/packages/block-library/src/navigation/edit/menu-inspector-controls.js b/packages/block-library/src/navigation/edit/menu-inspector-controls.js index 9f92d130ff14d..330812ef3e981 100644 --- a/packages/block-library/src/navigation/edit/menu-inspector-controls.js +++ b/packages/block-library/src/navigation/edit/menu-inspector-controls.js @@ -66,9 +66,12 @@ const MenuInspectorControls = ( { } ) => { const isOffCanvasNavigationEditorEnabled = window?.__experimentalEnableOffCanvasNavigationEditor === true; + const menuControlsSlot = window?.__experimentalEnableBlockInspectorTabs + ? 'list' + : undefined; return ( - + Date: Fri, 18 Nov 2022 11:40:04 +0100 Subject: [PATCH 049/380] Fix NavigableRegion README (#45879) --- .../src/components/navigable-region/README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/interface/src/components/navigable-region/README.md b/packages/interface/src/components/navigable-region/README.md index 6f98d17d79c45..5a931475443b1 100644 --- a/packages/interface/src/components/navigable-region/README.md +++ b/packages/interface/src/components/navigable-region/README.md @@ -4,8 +4,6 @@ It also renders a child `div` element that is responsible to set a negative `z-index` stack level, to make sure the focus style is always visible, regardless of other elements that may cut-off the focus style outline otherwise. -It can use a CSS animation via the `motion` component. - ## Props ### children @@ -29,10 +27,10 @@ A meaningful name for the ARIA landmark region. - Type: `String` - Required: Yes -### motionProps +### as -Properties of `motionProps` object will be used by the `motion` component to set a CSS animation on the wrapper div. +The component used as the root of the region. Defaults to a `div` element. -- Type: `Object` -- Required: No -- Default: `{}` +- Type: `Component` +- Required: no +- Default: `div` From 9e6e8c9cb7e8cf35ea148d28dfb7dc21b1c4b915 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 18 Nov 2022 10:42:03 +0000 Subject: [PATCH 050/380] Nav offcanvas editor: add simple back button to inspector controls (#45852) * Add very simple back button to block inspector * Enable for experiment only * Change button description label --- .../src/components/block-card/index.js | 30 +++++++++++++++++-- .../src/components/block-inspector/index.js | 23 ++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index 21832629b9557..6362b1b4ca2c7 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -2,13 +2,22 @@ * WordPress dependencies */ import deprecated from '@wordpress/deprecated'; - +import { Button } from '@wordpress/components'; +import { chevronLeft, chevronRight } from '@wordpress/icons'; +import { __, isRTL } from '@wordpress/i18n'; /** * Internal dependencies */ import BlockIcon from '../block-icon'; -function BlockCard( { title, icon, description, blockType } ) { +function BlockCard( { + title, + icon, + description, + blockType, + parentBlockClientId, + handleBackButton, +} ) { if ( blockType ) { deprecated( '`blockType` property in `BlockCard component`', { since: '5.7', @@ -16,8 +25,25 @@ function BlockCard( { title, icon, description, blockType } ) { } ); ( { title, icon, description } = blockType ); } + + const isOffCanvasNavigationEditorEnabled = + window?.__experimentalEnableOffCanvasNavigationEditor === true; + return (
    + { isOffCanvasNavigationEditorEnabled && parentBlockClientId && ( + ) } renderContent={ () => test } + popoverProps={ { 'data-testid': 'popover' } } /> ); - expectButtonExpanded( dropdownContainer, false ); - expectPopoverVisible( dropdownContainer, false ); + const button = screen.getByRole( 'button', { expanded: false } ); + + expect( button ).toBeVisible(); + expect( screen.queryByTestId( 'popover' ) ).not.toBeInTheDocument(); - const button = getButtonElement( dropdownContainer ); - fireEvent.click( button ); + await user.click( button ); - expectButtonExpanded( dropdownContainer, true ); - expectPopoverVisible( dropdownContainer, true ); + expect( + screen.getByRole( 'button', { expanded: true } ) + ).toBeVisible(); + + await waitFor( () => + expect( screen.getByTestId( 'popover' ) ).toBeVisible() + ); + + // Cleanup remaining effects, like the delayed popover positioning + unmount(); } ); - it( 'should close the dropdown when calling onClose', () => { - const { - container: { firstChild: dropdownContainer }, - } = render( + it( 'should close the dropdown when calling onClose', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + render( { aria-expanded={ isOpen } onClick={ onToggle } > - Toggleee + Toggle , , ] } renderContent={ () => null } + popoverProps={ { 'data-testid': 'popover' } } /> ); - expectPopoverVisible( dropdownContainer, false ); + expect( screen.queryByTestId( 'popover' ) ).not.toBeInTheDocument(); - const openButton = getOpenCloseButton( dropdownContainer, '.open' ); - fireEvent.click( openButton ); + await user.click( screen.getByRole( 'button', { name: 'Toggle' } ) ); - expectPopoverVisible( dropdownContainer, true ); + await waitFor( () => + expect( screen.getByTestId( 'popover' ) ).toBeVisible() + ); - const closeButton = getOpenCloseButton( dropdownContainer, '.close' ); - fireEvent.click( closeButton ); + await user.click( screen.getByRole( 'button', { name: 'close' } ) ); - expectPopoverVisible( dropdownContainer, false ); + expect( screen.queryByTestId( 'popover' ) ).not.toBeInTheDocument(); } ); } ); From b862a97ba27b1b3068330c62938d3f6496f4471c Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 18 Nov 2022 23:38:39 +0200 Subject: [PATCH 059/380] Element: Fix no-node-access in createInterpolateElement (#45894) --- .../element/src/test/create-interpolate-element.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/element/src/test/create-interpolate-element.js b/packages/element/src/test/create-interpolate-element.js index b8c5d4757e1cb..e78d3b1c55844 100644 --- a/packages/element/src/test/create-interpolate-element.js +++ b/packages/element/src/test/create-interpolate-element.js @@ -209,15 +209,13 @@ describe( 'createInterpolateElement', () => { }; const { container, rerender } = render( ); - expect( container.firstChild ).toContainHTML( 'string!' ); - expect( container.firstChild ).not.toContainHTML( '' ); + expect( container ).toContainHTML( 'string!' ); + expect( container ).not.toContainHTML( '' ); rerender( ); - expect( container.firstChild ).toContainHTML( - 'string!' - ); - expect( container.firstChild ).not.toContainHTML( '' ); + expect( container ).toContainHTML( 'string!' ); + expect( container ).not.toContainHTML( '' ); } ); it( 'handles parsing emojii correctly', () => { const testString = '👳‍♀️🚨🤷‍♂️⛈️fully here'; From 109aec494a01414b5abc83b1999bd610b8ae21c7 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Fri, 18 Nov 2022 23:38:59 +0200 Subject: [PATCH 060/380] Components: Fix no-node-access in Sandbox tests (#45908) --- packages/components/src/sandbox/test/index.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/components/src/sandbox/test/index.js b/packages/components/src/sandbox/test/index.js index d91dce1842199..f25bdca675cbf 100644 --- a/packages/components/src/sandbox/test/index.js +++ b/packages/components/src/sandbox/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { fireEvent, render, screen } from '@testing-library/react'; +import { fireEvent, render, screen, within } from '@testing-library/react'; /** * WordPress dependencies @@ -16,15 +16,15 @@ import Sandbox from '../'; describe( 'Sandbox', () => { const TestWrapper = () => { const [ html, setHtml ] = useState( - // MuatationObserver implementation from JSDom does not work as intended + // MutationObserver implementation from JSDom does not work as intended // with iframes so we need to ignore it for the time being. '' + - '' + '' ); const updateHtml = () => { setHtml( - '' + '' ); }; @@ -33,18 +33,19 @@ describe( 'Sandbox', () => { - +
    ); }; it( 'should rerender with new emdeded content if html prop changes', () => { - const { container } = render( ); + render( ); - const iframe = container.querySelector( '.components-sandbox' ); + const iframe = screen.getByTitle( 'Sandbox Title' ); - let sandboxedIframe = - iframe.contentWindow.document.body.querySelector( '.mock-iframe' ); + let sandboxedIframe = within( + iframe.contentWindow.document.body + ).getByTitle( 'Mock Iframe' ); expect( sandboxedIframe ).toHaveAttribute( 'src', @@ -53,8 +54,9 @@ describe( 'Sandbox', () => { fireEvent.click( screen.getByRole( 'button' ) ); - sandboxedIframe = - iframe.contentWindow.document.body.querySelector( '.mock-iframe' ); + sandboxedIframe = within( + iframe.contentWindow.document.body + ).getByTitle( 'Mock Iframe' ); expect( sandboxedIframe ).toHaveAttribute( 'src', From 8795c6e336d7cdf69caa02762bb3a0f4681c6fa7 Mon Sep 17 00:00:00 2001 From: Marissa <85708316+marissa-makes@users.noreply.github.com> Date: Sat, 19 Nov 2022 00:01:33 -0500 Subject: [PATCH 061/380] BlockVariationPicker: Remove unnecessary aria role (#45916) --- .../src/components/block-variation-picker/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-variation-picker/index.js b/packages/block-editor/src/components/block-variation-picker/index.js index 24bdfa272c34b..27e214ec39bb0 100644 --- a/packages/block-editor/src/components/block-variation-picker/index.js +++ b/packages/block-editor/src/components/block-variation-picker/index.js @@ -49,10 +49,7 @@ function BlockVariationPicker( { className="block-editor-block-variation-picker__variation" label={ variation.description || variation.title } /> - + { variation.title } From 9d936e44729d65d1522f1f2eccffd587c205f420 Mon Sep 17 00:00:00 2001 From: Seth Miller Date: Sun, 20 Nov 2022 19:19:35 -0500 Subject: [PATCH 062/380] Added InspectorControls import to example (#45872) * Added InspectorControls import to example Fixes InspectorControls is not defined * Update docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md Co-authored-by: Ryan Welcher --- .../block-tutorial/extending-the-query-loop-block.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md index 3956971645f86..e43dcb9727088 100644 --- a/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md +++ b/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md @@ -192,6 +192,8 @@ Notice that we have also disabled the `postType` control. When the user selects Because our plugin uses custom attributes that we need to query, we want to add our own controls to allow the users to select those instead of the ones we have just disabled from the core inspector controls. We can do this via a [React HOC](https://reactjs.org/docs/higher-order-components.html) hooked into a [block filter](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/), like so: ```jsx +import { InspectorControls } from '@wordpress/block-editor'; + export const withBookQueryControls = ( BlockEdit ) => ( props ) => { // We only want to add these controls if it is our variation, // so here we can implement a custom logic to check for that, similiar From 5046795e516f6cb02b9382c3354ce48dea3bda73 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Sat, 19 Nov 2022 18:52:51 -0700 Subject: [PATCH 063/380] Perf Tests: Fix test build process to call wp-scripts In #45284 we started skipping the generate-types phase of the build when running the performance tests. This was done to avoid the time it takes to generate those types when they are ignored by the rest of the test suite. In that patch we accidentally skipped running `wp-scripts build`, and in this patch we're bringing that back to fix the mistake. --- bin/plugin/commands/performance.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/plugin/commands/performance.js b/bin/plugin/commands/performance.js index a7f9c81e8cebb..31a537f373731 100644 --- a/bin/plugin/commands/performance.js +++ b/bin/plugin/commands/performance.js @@ -276,7 +276,7 @@ async function runPerformanceTests( branches, options ) { log( ` >> Building the ${ fancyBranch } branch` ); await runShellScript( - 'npm ci && node ./bin/packages/build.js', + 'npm ci && npm run prebuild:packages && node ./bin/packages/build.js && npx wp-scripts build', buildPath ); } From 35f25ab846f30eccbbf48ea6911e88f35f030c5b Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Sun, 20 Nov 2022 22:00:48 -0700 Subject: [PATCH 064/380] Add submenu menu item to list view (#45794) --- .../src/components/off-canvas-editor/block.js | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/off-canvas-editor/block.js b/packages/block-editor/src/components/off-canvas-editor/block.js index 50119ebddfb57..a0bf8529a4d01 100644 --- a/packages/block-editor/src/components/off-canvas-editor/block.js +++ b/packages/block-editor/src/components/off-canvas-editor/block.js @@ -6,10 +6,11 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { hasBlockSupport } from '@wordpress/blocks'; +import { createBlock, hasBlockSupport } from '@wordpress/blocks'; import { __experimentalTreeGridCell as TreeGridCell, __experimentalTreeGridItem as TreeGridItem, + MenuItem, } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { moreVertical } from '@wordpress/icons'; @@ -59,7 +60,7 @@ function ListViewBlock( { } ) { const cellRef = useRef( null ); const [ isHovered, setIsHovered ] = useState( false ); - const { clientId } = block; + const { clientId, attributes } = block; const { isLocked, isContentLocked } = useBlockLock( clientId ); const forceSelectionContentLock = useSelect( @@ -86,7 +87,8 @@ function ListViewBlock( { ( isSelected && selectedClientIds[ selectedClientIds.length - 1 ] === clientId ); - const { toggleBlockHighlight } = useDispatch( blockEditorStore ); + const { replaceBlock, toggleBlockHighlight } = + useDispatch( blockEditorStore ); const blockInformation = useBlockDisplayInformation( clientId ); const blockName = useSelect( @@ -355,7 +357,34 @@ function ListViewBlock( { } } disableOpenOnArrowDown __experimentalSelectBlock={ updateSelection } - /> + > + { ( { onClose } ) => ( + { + const newLink = createBlock( + 'core/navigation-link' + ); + const newSubmenu = createBlock( + 'core/navigation-submenu', + attributes, + block.innerBlocks + ? [ + ...block.innerBlocks, + newLink, + ] + : [ newLink ] + ); + replaceBlock( + clientId, + newSubmenu + ); + onClose(); + } } + > + { __( 'Add a submenu item' ) } + + ) } + ) } From 1b69ba2b9b42fe1b6fee765d06c401dd1ac710c6 Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Sun, 20 Nov 2022 23:02:25 -0800 Subject: [PATCH 065/380] Storybook: Opt in to story store v7 (#42486) * Opt in to code-split stories * Revert glob * Move story utility files to subfolders Co-authored-by: Lena Morita --- .../src/components/inserter/stories/index.js | 2 +- .../inserter/stories/{ => utils}/fixtures.js | 0 .../src/navigation/stories/index.js | 12 ++++----- .../stories/{ => utils}/controlled-state.js | 6 ++--- .../navigation/stories/{ => utils}/default.js | 6 ++--- .../navigation/stories/{ => utils}/group.js | 8 +++--- .../stories/{ => utils}/hide-if-empty.js | 6 ++--- .../stories/{ => utils}/more-examples.js | 8 +++--- .../navigation/stories/{ => utils}/search.js | 10 ++++---- .../src/tools-panel/stories/index.js | 2 +- .../tools-panel-with-item-group-slot.js | 25 +++++++++++-------- packages/components/tsconfig.json | 4 +-- storybook/main.js | 2 ++ 13 files changed, 48 insertions(+), 43 deletions(-) rename packages/block-editor/src/components/inserter/stories/{ => utils}/fixtures.js (100%) rename packages/components/src/navigation/stories/{ => utils}/controlled-state.js (96%) rename packages/components/src/navigation/stories/{ => utils}/default.js (93%) rename packages/components/src/navigation/stories/{ => utils}/group.js (86%) rename packages/components/src/navigation/stories/{ => utils}/hide-if-empty.js (90%) rename packages/components/src/navigation/stories/{ => utils}/more-examples.js (96%) rename packages/components/src/navigation/stories/{ => utils}/search.js (89%) rename packages/components/src/tools-panel/stories/{ => utils}/tools-panel-with-item-group-slot.js (92%) diff --git a/packages/block-editor/src/components/inserter/stories/index.js b/packages/block-editor/src/components/inserter/stories/index.js index c74ca5fe79ee1..f6949653c8787 100644 --- a/packages/block-editor/src/components/inserter/stories/index.js +++ b/packages/block-editor/src/components/inserter/stories/index.js @@ -3,7 +3,7 @@ */ import BlockLibrary from '../library'; import BlockEditorProvider from '../../provider'; -import { patternCategories, patterns, reusableBlocks } from './fixtures'; +import { patternCategories, patterns, reusableBlocks } from './utils/fixtures'; import Inserter from '../'; export default { title: 'BlockEditor/Inserter' }; diff --git a/packages/block-editor/src/components/inserter/stories/fixtures.js b/packages/block-editor/src/components/inserter/stories/utils/fixtures.js similarity index 100% rename from packages/block-editor/src/components/inserter/stories/fixtures.js rename to packages/block-editor/src/components/inserter/stories/utils/fixtures.js diff --git a/packages/components/src/navigation/stories/index.js b/packages/components/src/navigation/stories/index.js index 0c28f8c58347d..8fbdde44fe7df 100644 --- a/packages/components/src/navigation/stories/index.js +++ b/packages/components/src/navigation/stories/index.js @@ -6,12 +6,12 @@ import NavigationBackButton from '../back-button'; import NavigationGroup from '../group'; import NavigationItem from '../item'; import NavigationMenu from '../menu'; -import { DefaultStory } from './default'; -import { GroupStory } from './group'; -import { ControlledStateStory } from './controlled-state'; -import { SearchStory } from './search'; -import { MoreExamplesStory } from './more-examples'; -import { HideIfEmptyStory } from './hide-if-empty'; +import { DefaultStory } from './utils/default'; +import { GroupStory } from './utils/group'; +import { ControlledStateStory } from './utils/controlled-state'; +import { SearchStory } from './utils/search'; +import { MoreExamplesStory } from './utils/more-examples'; +import { HideIfEmptyStory } from './utils/hide-if-empty'; import './style.css'; export default { diff --git a/packages/components/src/navigation/stories/controlled-state.js b/packages/components/src/navigation/stories/utils/controlled-state.js similarity index 96% rename from packages/components/src/navigation/stories/controlled-state.js rename to packages/components/src/navigation/stories/utils/controlled-state.js index 68c1be54f1bff..ed7d652d0df77 100644 --- a/packages/components/src/navigation/stories/controlled-state.js +++ b/packages/components/src/navigation/stories/utils/controlled-state.js @@ -7,9 +7,9 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function ControlledStateStory() { const [ activeItem, setActiveItem ] = useState( 'item-1' ); diff --git a/packages/components/src/navigation/stories/default.js b/packages/components/src/navigation/stories/utils/default.js similarity index 93% rename from packages/components/src/navigation/stories/default.js rename to packages/components/src/navigation/stories/utils/default.js index cbcfe53338811..569cb8aa17b4b 100644 --- a/packages/components/src/navigation/stories/default.js +++ b/packages/components/src/navigation/stories/utils/default.js @@ -6,9 +6,9 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function DefaultStory() { const [ activeItem, setActiveItem ] = useState( 'item-1' ); diff --git a/packages/components/src/navigation/stories/group.js b/packages/components/src/navigation/stories/utils/group.js similarity index 86% rename from packages/components/src/navigation/stories/group.js rename to packages/components/src/navigation/stories/utils/group.js index 1c919d50464f2..7e11071a63b9a 100644 --- a/packages/components/src/navigation/stories/group.js +++ b/packages/components/src/navigation/stories/utils/group.js @@ -6,10 +6,10 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; -import NavigationGroup from '../group'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; +import NavigationGroup from '../../group'; export function GroupStory() { const [ activeItem, setActiveItem ] = useState( 'item-1' ); diff --git a/packages/components/src/navigation/stories/hide-if-empty.js b/packages/components/src/navigation/stories/utils/hide-if-empty.js similarity index 90% rename from packages/components/src/navigation/stories/hide-if-empty.js rename to packages/components/src/navigation/stories/utils/hide-if-empty.js index 0977a41ab1846..ef9e889932a89 100644 --- a/packages/components/src/navigation/stories/hide-if-empty.js +++ b/packages/components/src/navigation/stories/utils/hide-if-empty.js @@ -1,9 +1,9 @@ /** * Internal dependencies */ -import Navigation from '..'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function HideIfEmptyStory() { return ( diff --git a/packages/components/src/navigation/stories/more-examples.js b/packages/components/src/navigation/stories/utils/more-examples.js similarity index 96% rename from packages/components/src/navigation/stories/more-examples.js rename to packages/components/src/navigation/stories/utils/more-examples.js index 2887ab1241c0c..43bd33835db37 100644 --- a/packages/components/src/navigation/stories/more-examples.js +++ b/packages/components/src/navigation/stories/utils/more-examples.js @@ -7,10 +7,10 @@ import { Icon, wordpress, home } from '@wordpress/icons'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationGroup from '../group'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; +import Navigation from '../..'; +import NavigationGroup from '../../group'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; export function MoreExamplesStory() { const [ activeItem, setActiveItem ] = useState( 'child-1' ); diff --git a/packages/components/src/navigation/stories/search.js b/packages/components/src/navigation/stories/utils/search.js similarity index 89% rename from packages/components/src/navigation/stories/search.js rename to packages/components/src/navigation/stories/utils/search.js index d9bcd405cbc34..6fb1a17697023 100644 --- a/packages/components/src/navigation/stories/search.js +++ b/packages/components/src/navigation/stories/utils/search.js @@ -6,11 +6,11 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import Navigation from '..'; -import NavigationGroup from '../group'; -import NavigationItem from '../item'; -import NavigationMenu from '../menu'; -import { normalizedSearch } from '../utils'; +import Navigation from '../..'; +import NavigationGroup from '../../group'; +import NavigationItem from '../../item'; +import NavigationMenu from '../../menu'; +import { normalizedSearch } from '../../utils'; const searchItems = [ { item: 'foo', title: 'Foo' }, diff --git a/packages/components/src/tools-panel/stories/index.js b/packages/components/src/tools-panel/stories/index.js index 9f03ebbede637..49f9d366587cb 100644 --- a/packages/components/src/tools-panel/stories/index.js +++ b/packages/components/src/tools-panel/stories/index.js @@ -494,7 +494,7 @@ export const WithConditionallyRenderedControl = () => { ); }; -export { ToolsPanelWithItemGroupSlot } from './tools-panel-with-item-group-slot'; +export { ToolsPanelWithItemGroupSlot } from './utils/tools-panel-with-item-group-slot'; const PanelWrapperView = styled.div` font-size: 13px; diff --git a/packages/components/src/tools-panel/stories/tools-panel-with-item-group-slot.js b/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js similarity index 92% rename from packages/components/src/tools-panel/stories/tools-panel-with-item-group-slot.js rename to packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js index 717fd3a448527..d1895324df4c3 100644 --- a/packages/components/src/tools-panel/stories/tools-panel-with-item-group-slot.js +++ b/packages/components/src/tools-panel/stories/utils/tools-panel-with-item-group-slot.js @@ -12,17 +12,20 @@ import { useContext, useState } from '@wordpress/element'; /** * Internal dependencies */ -import Button from '../../button'; -import ColorIndicator from '../../color-indicator'; -import ColorPalette from '../../color-palette'; -import Dropdown from '../../dropdown'; -import Panel from '../../panel'; -import { FlexItem } from '../../flex'; -import { HStack } from '../../h-stack'; -import { Item, ItemGroup } from '../../item-group'; -import { ToolsPanel, ToolsPanelItem, ToolsPanelContext } from '..'; -import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; -import { useCx } from '../../utils'; +import Button from '../../../button'; +import ColorIndicator from '../../../color-indicator'; +import ColorPalette from '../../../color-palette'; +import Dropdown from '../../../dropdown'; +import Panel from '../../../panel'; +import { FlexItem } from '../../../flex'; +import { HStack } from '../../../h-stack'; +import { Item, ItemGroup } from '../../../item-group'; +import { ToolsPanel, ToolsPanelItem, ToolsPanelContext } from '../..'; +import { + createSlotFill, + Provider as SlotFillProvider, +} from '../../../slot-fill'; +import { useCx } from '../../../utils'; // Available border colors. const colors = [ diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 107f0ffe58641..04a8eaa8d4d9e 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -37,8 +37,8 @@ "src/**/*.ios.js", "src/**/*.native.js", "src/**/react-native-*", - "src/**/stories/**.js", // only exclude js files, tsx files should be checked - "src/**/test/**.js", // only exclude js files, ts{x} files should be checked + "src/**/stories/**/*.js", // only exclude js files, tsx files should be checked + "src/**/test/**/*.js", // only exclude js files, ts{x} files should be checked "src/index.js", "src/alignment-matrix-control", "src/angle-picker-control", diff --git a/storybook/main.js b/storybook/main.js index 717f42f7fb5e6..29880d6455700 100644 --- a/storybook/main.js +++ b/storybook/main.js @@ -25,9 +25,11 @@ module.exports = { '@storybook/addon-actions', 'storybook-source-link', ], + framework: '@storybook/react', features: { babelModeV7: true, emotionAlias: false, + storyStoreV7: true, }, // Workaround: // https://github.com/storybookjs/storybook/issues/12270 From 5c445c240f2aeb5823fe6ea3a97c24aadf85aee3 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 21 Nov 2022 18:56:21 +1100 Subject: [PATCH 066/380] Style Engine: add first draft of contributing doc (#45930) * Initial commit. * Adding `@group` to style engine tests so they can be run altogether Finishing up first draft of CONTRIBUTING.md notes * Formattings * Apply suggestions from code review Co-authored-by: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Co-authored-by: Andrew Serong <14988353+andrewserong@users.noreply.github.com> --- packages/style-engine/CONTRIBUTING.md | 40 +++++++++++++++++++ packages/style-engine/README.md | 2 + ...-wp-style-engine-css-declarations-test.php | 1 + .../class-wp-style-engine-css-rule-test.php | 1 + ...s-wp-style-engine-css-rules-store-test.php | 1 + .../class-wp-style-engine-processor-test.php | 1 + phpunit/style-engine/style-engine-test.php | 2 + 7 files changed, 48 insertions(+) create mode 100644 packages/style-engine/CONTRIBUTING.md diff --git a/packages/style-engine/CONTRIBUTING.md b/packages/style-engine/CONTRIBUTING.md new file mode 100644 index 0000000000000..70bfddb8eb501 --- /dev/null +++ b/packages/style-engine/CONTRIBUTING.md @@ -0,0 +1,40 @@ +# Contributing + +This document contains information you might need to know when extending or debugging Style Engine code. + +## Workflow and build tooling + +The Style Engine PHP and Javascript (JS) files exist inside the `style-engine` package. + +In order to use the Style Engine in the Block Editor, these files must be compiled (in the case of JS) and copied to the build folder. + +When running the `npm run dev` script for example, webpack watches out for changes and will recompile/copy files as necessary if any changes are detected according to the rules in the [packages webpack config](https://github.com/WordPress/gutenberg/tree/HEAD/tools/webpack/packages.js). + +No other configuration is required for JS: webpack will compile and export the Style Engine code as it does with all dependencies listed in [package.json](https://github.com/WordPress/gutenberg/tree/HEAD/package.json). + +The PHP files for packages, however, have a couple of extra steps during the build process: + +1. Functions with the `wp_` prefix are replaced with `gutenberg_`. So, for example, `wp_some_function` becomes `gutenberg_some_function` in the build directory. The reason for this is so that the Block Editor can call Style Engine functions that may have evolved since, or have not yet been included in, any WordPress release. +2. For the same reasons, classes are given a `_Gutenberg` suffix: `WP_Style_Engine` becomes `WP_Style_Engine_Gutenberg`. The [packages webpack config](https://github.com/WordPress/gutenberg/tree/HEAD/tools/webpack/packages.js) contains a static list of PHP classes (`bundledPackagesPhpConfig`) that have to be copied and renamed during build. If you create a new PHP class in the Style Engine package, you should add your class name to the `replaceClasses` array. + +Remember: all PHP functions and methods inside the Style Engine package should use `wp_/WP_` prefixes. Usage outside of the package in Gutenberg can reference the `gutenberg` prefixes or suffixes from the built files. + +When updating existing PHP functions or methods, it's important to check the Block Editor codebase for calls to the equivalent `wp_` functions or classes as they may have to be updated to refer to `gutenberg_` or `_Gutenberg` in order for the updates to take effect. + +## Testing + +[JS unit tests](https://github.com/WordPress/gutenberg/tree/HEAD/packages/style-engine/src/test) are stored next to the source code in the `style-engine` package directory. + +To start the JS unit tests, run: + +`npm run test:unit packages/style-engine/src/test/` + +[PHP unit tests](https://github.com/WordPress/gutenberg/tree/HEAD/phpunit/style-engine) are located in the root `phpunit` directory. + +In order to test the latest version of the Style Engine and avoid conflicts with existing Core equivalents, all PHP unit tests call the `gutenberg_` functions and `_Gutenberg` classes. + +Therefore, Style Engine PHP source files should be parsed and copied to the build folder before running tests. During development, this will happen as part of the `npm run dev` script. You can also trigger a build by executing `npm run build`. + +To start the PHP unit tests, run: + +`npm run test:unit:php -- --group style-engine` diff --git a/packages/style-engine/README.md b/packages/style-engine/README.md index 317e48e0c37c4..999fab2aa835e 100644 --- a/packages/style-engine/README.md +++ b/packages/style-engine/README.md @@ -19,6 +19,8 @@ Upcoming tasks on the roadmap include, but are not limited to, the following: For more information about the roadmap, please refer to [Block editor styles: initiatives and goals](https://make.wordpress.org/core/2022/06/24/block-editor-styles-initiatives-and-goals/) and the [Github project board](https://github.com/orgs/WordPress/projects/19). +If you're making changes or additions to the Style Engine, please take a moment to read the [notes on contributing](https://github.com/WordPress/gutenberg/tree/HEAD/packages/style-engine/CONTRIBUTING.md). + ## Backend API ### wp_style_engine_get_styles() diff --git a/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php b/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php index 5380cc81d5831..011de140d0892 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-declarations-test.php @@ -9,6 +9,7 @@ /** * Tests registering, storing and generating CSS declarations. * + * @group style-engine * @coversDefaultClass WP_Style_Engine_CSS_Declarations_Gutenberg */ class WP_Style_Engine_CSS_Declarations_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php index 26ea41c7ce830..02482c70e4d84 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php @@ -9,6 +9,7 @@ /** * Tests for registering, storing and generating CSS rules. * + * @group style-engine * @coversDefaultClass WP_Style_Engine_CSS_Rule_Gutenberg */ class WP_Style_Engine_CSS_Rule_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php b/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php index 700f63c556c25..8529bff78e22c 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-rules-store-test.php @@ -9,6 +9,7 @@ /** * Tests for registering, storing and retrieving a collection of CSS Rules (a store). * + * @group style-engine * @coversDefaultClass WP_Style_Engine_CSS_Rules_Store_Gutenberg */ class WP_Style_Engine_CSS_Rules_Store_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/class-wp-style-engine-processor-test.php b/phpunit/style-engine/class-wp-style-engine-processor-test.php index cfbde08704bdc..18392f5156fcc 100644 --- a/phpunit/style-engine/class-wp-style-engine-processor-test.php +++ b/phpunit/style-engine/class-wp-style-engine-processor-test.php @@ -9,6 +9,7 @@ /** * Tests for compiling and rendering styles from a store of CSS rules. * + * @group style-engine * @coversDefaultClass WP_Style_Engine_Processor_Gutenberg */ class WP_Style_Engine_Processor_Test extends WP_UnitTestCase { diff --git a/phpunit/style-engine/style-engine-test.php b/phpunit/style-engine/style-engine-test.php index 5f4c57453e8a8..66d8dd6286527 100644 --- a/phpunit/style-engine/style-engine-test.php +++ b/phpunit/style-engine/style-engine-test.php @@ -8,6 +8,8 @@ /** * Tests for registering, storing and generating styles. + * + * @group style-engine */ class WP_Style_Engine_Test extends WP_UnitTestCase { /** From 8f57b443358aa8b098589f20ee7d0109bd592db2 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 21 Nov 2022 13:13:29 +0400 Subject: [PATCH 067/380] Site Editor: Fix template list width (#45888) --- packages/edit-site/src/components/list/style.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/list/style.scss b/packages/edit-site/src/components/list/style.scss index e6cf7220cdf60..f18e90c00fc61 100644 --- a/packages/edit-site/src/components/list/style.scss +++ b/packages/edit-site/src/components/list/style.scss @@ -44,9 +44,12 @@ .interface-interface-skeleton__content { background: $white; - align-items: center; padding: $grid-unit-20; + .interface-navigable-region__stacker { + align-items: center; + } + @include break-medium() { padding: $grid-unit * 9; } From abc8d7e850e0e6162725fe22f1b1d033f3d22ee4 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 21 Nov 2022 11:19:31 +0100 Subject: [PATCH 068/380] Cleanup the BlockPreview component (#45936) --- packages/base-styles/_z-index.scss | 1 - .../src/components/block-preview/README.md | 14 -------------- .../src/components/block-preview/index.js | 17 +++++------------ .../src/components/block-preview/live.js | 19 ------------------- .../src/components/block-preview/style.scss | 2 -- 5 files changed, 5 insertions(+), 48 deletions(-) delete mode 100644 packages/block-editor/src/components/block-preview/live.js diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 14305a9361197..9c7b55e78a69c 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -7,7 +7,6 @@ $z-layers: ( ".block-editor-block-switcher__arrow": 1, ".block-editor-block-list__block {core/image aligned wide or fullwide}": 20, ".block-library-classic__toolbar": 31, // When scrolled to top this toolbar needs to sit over block-editor-block-toolbar - ".block-editor-block-list__layout .reusable-block-indicator": 1, ".block-editor-block-list__block-selection-button": 22, ".components-form-toggle__input": 1, ".edit-post-text-editor__toolbar": 1, diff --git a/packages/block-editor/src/components/block-preview/README.md b/packages/block-editor/src/components/block-preview/README.md index 6df2cb6773878..0da7348817cd8 100644 --- a/packages/block-editor/src/components/block-preview/README.md +++ b/packages/block-editor/src/components/block-preview/README.md @@ -34,17 +34,3 @@ Width of the preview container in pixels. Controls at what size the blocks will - **Default** `undefined` Padding for the preview container body. - -### `__experimentalLive` - -- **Type** `Boolean` -- **Default:** `false` - -Enables displaying previews without an iframe container. - -### `__experimentalOnClick` - -- **Type** `Function` -- **Default:** `undefined` - -Use this callback in combination with `__experimentalLive`. The callback is attached to the preview container element. diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index b01411591f098..535a67438cad8 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -14,7 +14,6 @@ import { memo, useMemo } from '@wordpress/element'; * Internal dependencies */ import BlockEditorProvider from '../provider'; -import LiveBlockPreview from './live'; import AutoHeightBlockPreview from './auto'; import { store as blockEditorStore } from '../../store'; import { BlockListItems } from '../block-list'; @@ -23,8 +22,6 @@ export function BlockPreview( { blocks, __experimentalPadding = 0, viewportWidth = 1200, - __experimentalLive = false, - __experimentalOnClick, __experimentalMinHeight, } ) { const originalSettings = useSelect( @@ -44,15 +41,11 @@ export function BlockPreview( { } return ( - { __experimentalLive ? ( - - ) : ( - - ) } + ); } diff --git a/packages/block-editor/src/components/block-preview/live.js b/packages/block-editor/src/components/block-preview/live.js deleted file mode 100644 index 5015c778defe7..0000000000000 --- a/packages/block-editor/src/components/block-preview/live.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Internal dependencies - */ -import BlockList from '../block-list'; - -export default function LiveBlockPreview( { onClick } ) { - return ( -
    -
    - -
    -
    - ); -} diff --git a/packages/block-editor/src/components/block-preview/style.scss b/packages/block-editor/src/components/block-preview/style.scss index 9736d6f6784c4..54dad77698d94 100644 --- a/packages/block-editor/src/components/block-preview/style.scss +++ b/packages/block-editor/src/components/block-preview/style.scss @@ -29,8 +29,6 @@ min-height: auto; .block-editor-block-list__insertion-point, - .block-editor-block-drop-zone, - .reusable-block-indicator, .block-list-appender { display: none; } From 1906e3923ef138cfd5e6a3984981a425c3d00690 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 21 Nov 2022 12:38:16 +0200 Subject: [PATCH 069/380] Block Editor: Fix block alignment tests for React 18 (#45937) --- .../src/components/alignment-control/test/index.js | 5 ++++- .../src/components/block-alignment-control/test/index.js | 5 ++++- .../block-vertical-alignment-control/test/index.js | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/alignment-control/test/index.js b/packages/block-editor/src/components/alignment-control/test/index.js index 14f0d9a04e033..c792b3e9b7f82 100644 --- a/packages/block-editor/src/components/alignment-control/test/index.js +++ b/packages/block-editor/src/components/alignment-control/test/index.js @@ -52,7 +52,7 @@ describe( 'AlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align text \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); test( 'should call on change with undefined when a control is already active', async () => { diff --git a/packages/block-editor/src/components/block-alignment-control/test/index.js b/packages/block-editor/src/components/block-alignment-control/test/index.js index fd9f0f782e25d..abf2a0a7c448a 100644 --- a/packages/block-editor/src/components/block-alignment-control/test/index.js +++ b/packages/block-editor/src/components/block-alignment-control/test/index.js @@ -47,7 +47,7 @@ describe( 'BlockAlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); test( 'should call onChange with undefined, when the control is already active', async () => { diff --git a/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js b/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js index 2e54ee9026854..f2645357a197c 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js +++ b/packages/block-editor/src/components/block-vertical-alignment-control/test/index.js @@ -45,7 +45,7 @@ describe( 'BlockVerticalAlignmentUI', () => { advanceTimers: jest.advanceTimersByTime, } ); - render( + const { unmount } = render( { name: /^Align \w+$/, } ) ).toHaveLength( 3 ); + + // Cancel running effects, like delayed dropdown menu popover positioning. + unmount(); } ); it( 'should call onChange with undefined, when the control is already active', async () => { From bbc5489c991e2030f9da042683c1b172bfd85215 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 21 Nov 2022 13:14:47 +0200 Subject: [PATCH 070/380] Block Editor: Wait for popover positioning in `MediaReplaceFlow` tests (#45863) * Block Editor: Wait for popover positioning in MediaReplaceFlow tests * Remove an extra backtick * Use Element.closest() to locate closest popover * Fix comment * Remove an excess assertion * Reorder assertions * Introduce a less-specific way to assert positioning * Use the right element when locating the popover * Clarify element.style usage * Wait for the menu to get visible --- .../media-replace-flow/test/index.js | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/media-replace-flow/test/index.js b/packages/block-editor/src/components/media-replace-flow/test/index.js index 8d6bc6ca43378..d73da6c77c5d5 100644 --- a/packages/block-editor/src/components/media-replace-flow/test/index.js +++ b/packages/block-editor/src/components/media-replace-flow/test/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; /** @@ -32,6 +32,34 @@ function TestWrapper() { ); } +/** + * Returns the first found popover element up the DOM tree. + * + * @param {HTMLElement} element Element to start with. + * @return {HTMLElement|null} Popover element, or `null` if not found. + */ +function getWrappingPopoverElement( element ) { + return element.closest( '.components-popover' ); +} + +/** + * Asserts that the specified popover has already been positioned. + * Necessary because it will be positioned a bit later after it's displayed. + * + * We're intentionally not using `.toHaveStyle()` because we want to be + * less specific and avoid specific values for better test flexibility. + * + * @async + * + * @param {HTMLElement} popover Popover element. + */ +async function popoverIsPositioned( popover ) { + /* eslint-disable jest-dom/prefer-to-have-style */ + await waitFor( () => expect( popover.style.top ).not.toBe( '' ) ); + await waitFor( () => expect( popover.style.left ).not.toBe( '' ) ); + /* eslint-enable jest-dom/prefer-to-have-style */ +} + describe( 'General media replace flow', () => { it( 'renders successfully', () => { render( ); @@ -57,11 +85,11 @@ describe( 'General media replace flow', () => { name: 'Replace', } ) ); - const uploadMenu = screen.getByRole( 'menu' ); - expect( uploadMenu ).toBeInTheDocument(); - expect( uploadMenu ).not.toBeVisible(); + await popoverIsPositioned( getWrappingPopoverElement( uploadMenu ) ); + + await waitFor( () => expect( uploadMenu ).toBeVisible() ); } ); it( 'displays media URL', async () => { @@ -78,11 +106,13 @@ describe( 'General media replace flow', () => { } ) ); - expect( - screen.getByRole( 'link', { - name: 'example.media (opens in a new tab)', - } ) - ).toHaveAttribute( 'href', 'https://example.media' ); + const link = screen.getByRole( 'link', { + name: 'example.media (opens in a new tab)', + } ); + + await popoverIsPositioned( getWrappingPopoverElement( link ) ); + + expect( link ).toHaveAttribute( 'href', 'https://example.media' ); } ); it( 'edits media URL', async () => { @@ -99,6 +129,14 @@ describe( 'General media replace flow', () => { } ) ); + await popoverIsPositioned( + getWrappingPopoverElement( + screen.getByRole( 'link', { + name: 'example.media (opens in a new tab)', + } ) + ) + ); + await user.click( screen.getByRole( 'button', { name: 'Edit', From b55bd2335d32de45e4a7de63eff19ee1070fbce5 Mon Sep 17 00:00:00 2001 From: Miguel Torres Date: Mon, 21 Nov 2022 12:31:32 +0100 Subject: [PATCH 071/380] Ignore cached `wp_theme_has_theme_json` when `WP_DEBUG` is enabled (#45882) --- .../wordpress-6.2/get-global-styles-and-settings.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index b2637969fd8ef..be9781556868c 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -20,13 +20,17 @@ function wp_theme_has_theme_json() { $theme_has_support = wp_cache_get( $cache_key, $cache_group ); /** - * $theme_has_support is stored as a int in the cache. + * $theme_has_support is stored as an int in the cache. * * The reason not to store it as a boolean is to avoid working * with the $found parameter which apparently had some issues in some implementations * https://developer.wordpress.org/reference/functions/wp_cache_get/ */ - if ( 0 === $theme_has_support || 1 === $theme_has_support ) { + if ( + // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. + ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && + ( 0 === $theme_has_support || 1 === $theme_has_support ) + ) { return (bool) $theme_has_support; } From 7ef776b97e760b165a34493412b260fa8a23fd41 Mon Sep 17 00:00:00 2001 From: Vijayan Date: Mon, 21 Nov 2022 18:06:17 +0530 Subject: [PATCH 072/380] Fix link & code markdown (#45708) - Fix Modal component link - Fix code markdown at `Unsupported: Multiple instances` --- packages/components/src/confirm-dialog/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/confirm-dialog/README.md b/packages/components/src/confirm-dialog/README.md index 2f6a764f7f841..5ed3653489849 100644 --- a/packages/components/src/confirm-dialog/README.md +++ b/packages/components/src/confirm-dialog/README.md @@ -4,7 +4,7 @@ This feature is still experimental. "Experimental" means this is an early implementation subject to drastic and breaking changes.
    -`ConfirmDialog` is built of top of [`Modal`](/packages/components/src/modal/README.md] and displays a confirmation dialog, with _confirm_ and _cancel_ buttons. +`ConfirmDialog` is built of top of [`Modal`](/packages/components/src/modal/README.md) and displays a confirmation dialog, with _confirm_ and _cancel_ buttons. The dialog is confirmed by clicking the _confirm_ button or by pressing the `Enter` key. It is cancelled (closed) by clicking the _cancel_ button, by pressing the `ESC` key, or by clicking outside the dialog focus (i.e, the overlay). @@ -72,7 +72,7 @@ function Example() { ### Unsupported: Multiple instances -Multiple `ConfirmDialog's is an edge case that's currently not officially supported by this component. At the moment, new instances will end up closing the last instance due to the way the `Modal` is implemented. +Multiple `ConfirmDialog`s is an edge case that's currently not officially supported by this component. At the moment, new instances will end up closing the last instance due to the way the `Modal` is implemented. ## Custom Types From ba9f76cf9b7958cb7d9647577309829cdec188ab Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Mon, 21 Nov 2022 12:58:47 +0000 Subject: [PATCH 073/380] Site Logo: Apply width to logo container in editor (#45821) * Apply width to logo container * Move width to placeholder --- packages/block-library/src/site-logo/edit.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 89b9e3f6eba01..8b85798baee71 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -512,6 +512,9 @@ export default function LogoEdit( { className={ placeholderClassName } preview={ logoImage } withIllustration={ true } + style={ { + width, + } } > { content } From 5122e650ab09604645011dd11cd2cfc83c00f70f Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:11:33 +0200 Subject: [PATCH 074/380] Components: Fix `no-node-access` in `Grid` tests (#45900) * Components: Fix no-node-access in Grid tests * role -> data-testid --- packages/components/src/grid/test/grid.tsx | 62 +++++++++++----------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/components/src/grid/test/grid.tsx b/packages/components/src/grid/test/grid.tsx index 791dfa4f52216..150dac578c80a 100644 --- a/packages/components/src/grid/test/grid.tsx +++ b/packages/components/src/grid/test/grid.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; /** * Internal dependencies @@ -12,14 +12,14 @@ import CONFIG from '../../utils/config-values'; describe( 'props', () => { test( 'should render correctly', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: 'repeat( 2, 1fr )', gap: `calc( ${ CONFIG.gridBase } * 3 )`, @@ -27,15 +27,15 @@ describe( 'props', () => { } ); test( 'should render gap', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: 'repeat( 3, 1fr )', gap: `calc( ${ CONFIG.gridBase } * 4 )`, @@ -43,60 +43,60 @@ describe( 'props', () => { } ); test( 'should render custom columns', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: 'repeat( 7, 1fr )', } ); } ); test( 'should render custom rows', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateRows: 'repeat( 7, 1fr )', } ); } ); test( 'should render align', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { alignItems: 'flex-start', display: 'grid', } ); } ); test( 'should render alignment spaced', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', alignItems: 'center', justifyContent: 'space-between', @@ -104,60 +104,60 @@ describe( 'props', () => { } ); test( 'should render justify', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', justifyContent: 'flex-start', } ); } ); test( 'should render isInline', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'inline-grid', gridTemplateColumns: 'repeat( 3, 1fr )', } ); } ); test( 'should render custom templateColumns', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateColumns: '1fr auto 1fr', } ); } ); test( 'should render custom templateRows', () => { - const { container } = render( - + render( + ); - expect( container.firstChild ).toHaveStyle( { + expect( screen.getByTestId( 'grid' ) ).toHaveStyle( { display: 'grid', gridTemplateRows: '1fr auto 1fr', } ); From 37fc2a1dbc1671dabe3a4771b5b030814b452aae Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Mon, 21 Nov 2022 16:21:13 +0200 Subject: [PATCH 075/380] Enable easier drag and drop for navigation building (#45906) * tweaks the way navigation link and navigation submenu work to enable drag and drop in list view * Adds deps list to effects for submenu and navigation link blocks. Co-authored-by: Daniel Richards <677833+talldan@users.noreply.github.com> Co-authored-by: Maggie <3593343+MaggieCabrera@users.noreply.github.com> Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> Co-authored-by: Daniel Richards <677833+talldan@users.noreply.github.com> Co-authored-by: Maggie <3593343+MaggieCabrera@users.noreply.github.com> Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> --- .../block-library/src/navigation-link/edit.js | 25 +++++++++++++++++-- .../src/navigation-submenu/edit.js | 7 ++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index ed0acced4efe8..30a2f9ebb4b52 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -32,6 +32,7 @@ import { useBlockProps, store as blockEditorStore, getColorClassName, + useInnerBlocksProps, } from '@wordpress/block-editor'; import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url'; import { @@ -527,7 +528,9 @@ export default function NavigationLinkEdit( { const newSubmenu = createBlock( 'core/navigation-submenu', attributes, - innerBlocks + innerBlocks.length > 0 + ? innerBlocks + : [ createBlock( 'core/navigation-link' ) ] ); replaceBlock( clientId, newSubmenu ); } @@ -540,11 +543,14 @@ export default function NavigationLinkEdit( { if ( ! url ) { setIsLinkOpen( true ); } + }, [ url ] ); + + useEffect( () => { // If block has inner blocks, transform to Submenu. if ( hasChildren ) { transformToSubmenu(); } - }, [] ); + }, [ hasChildren ] ); /** * The hook shouldn't be necessary but due to a focus loss happening @@ -675,6 +681,20 @@ export default function NavigationLinkEdit( { onKeyDown, } ); + const ALLOWED_BLOCKS = [ + 'core/navigation-link', + 'core/navigation-submenu', + ]; + const DEFAULT_BLOCK = { + name: 'core/navigation-link', + }; + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks: ALLOWED_BLOCKS, + __experimentalDefaultBlock: DEFAULT_BLOCK, + __experimentalDirectInsert: true, + renderAppender: false, + } ); + if ( ! url || isInvalid || isDraft ) { blockProps.onClick = () => setIsLinkOpen( true ); } @@ -915,6 +935,7 @@ export default function NavigationLinkEdit( { ) } +
    ); diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index c1dd94a0c1034..9810c7ebab05c 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -541,6 +541,13 @@ export default function NavigationSubmenuEdit( { replaceBlock( clientId, newLinkBlock ); } + useEffect( () => { + // If block is empty, transform to Navigation Link. + if ( ! hasChildren ) { + transformToLink(); + } + }, [ hasChildren ] ); + const canConvertToLink = ! selectedBlockHasChildren || onlyDescendantIsEmptyLink; From f02087f7f8878a566b63cc02493ee3b24c855e34 Mon Sep 17 00:00:00 2001 From: chad1008 <13856531+chad1008@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:03:14 -0500 Subject: [PATCH 076/380] Components: Remove CircleIndicatorWrapper `focus-visible` outline (#45758) --- packages/components/CHANGELOG.md | 3 +++ .../styles/angle-picker-control-styles.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 2e47db6c77868..c57f3b01d5825 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -13,6 +13,9 @@ - `ColorPalette`, `BorderBox`, `BorderBoxControl`: polish and DRY prop types, add default values ([#45463](https://github.com/WordPress/gutenberg/pull/45463)). - `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). +### Internal +- `AnglePickerControl`: remove `:focus-visible' outline on `CircleOutlineWrapper` ([#45758](https://github.com/WordPress/gutenberg/pull/45758)) + ### Bug Fix - `FormTokenField`: Fix duplicate input in IME composition ([#45607](https://github.com/WordPress/gutenberg/pull/45607)). diff --git a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js index 1a7ed713d72f1..29b38850c055f 100644 --- a/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js +++ b/packages/components/src/angle-picker-control/styles/angle-picker-control-styles.js @@ -42,6 +42,10 @@ export const CircleIndicatorWrapper = styled.div` position: relative; width: 100%; height: 100%; + + :focus-visible { + outline: none; + } `; export const CircleIndicator = styled.div` From 6fa02c7e364424a907838dde14815d771e6e483a Mon Sep 17 00:00:00 2001 From: hiyascout <113260898+hiyascout@users.noreply.github.com> Date: Mon, 21 Nov 2022 16:58:43 -0800 Subject: [PATCH 077/380] Update applying-styles-with-stylesheets.md (#45925) Fixed two typos in opening paragraph. ('someway' and 'this guides walks') --- .../block-tutorial/applying-styles-with-stylesheets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md index 815adfdf80c26..e668a6ac76238 100644 --- a/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md +++ b/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md @@ -2,7 +2,7 @@ ## Overview -A block typically inserts markup (HTML) into post content that you want to style in someway. This guides walks through a few different ways you can use CSS with the block editor and how to work with styles and stylesheets. +A block typically inserts markup (HTML) into post content that you want to style in some way. This guide walks through a few different ways you can use CSS with the block editor and how to work with styles and stylesheets. ## Before you start From 453866d96b2b1857fd8a2bc9340f3856336c07c8 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Tue, 22 Nov 2022 10:39:14 +0200 Subject: [PATCH 078/380] Add a starting page for page list block's hierarchy (#45861) * adds a rootPageID attribute and handling * adds UI to control rootPageID * Rename root to parent, generate fixtures Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> Co-authored-by: Joen A. <1204802+jasmussen@users.noreply.github.com> * PHP lint * PHP Lint Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> Co-authored-by: Joen A. <1204802+jasmussen@users.noreply.github.com> --- docs/reference-guides/core-blocks.md | 2 +- .../block-library/src/page-list/block.json | 7 +- packages/block-library/src/page-list/edit.js | 65 +++++++++++++++++-- .../block-library/src/page-list/index.php | 9 +++ .../fixtures/blocks/core__page-list.json | 4 +- 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 65e3cb1f65c8f..8650c9d7d3274 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -429,7 +429,7 @@ Display a list of all pages. ([Source](https://github.com/WordPress/gutenberg/tr - **Name:** core/page-list - **Category:** widgets - **Supports:** ~~html~~, ~~reusable~~ -- **Attributes:** +- **Attributes:** rootPageID ## Paragraph diff --git a/packages/block-library/src/page-list/block.json b/packages/block-library/src/page-list/block.json index 3068a1fb8bc00..144cecb428396 100644 --- a/packages/block-library/src/page-list/block.json +++ b/packages/block-library/src/page-list/block.json @@ -7,7 +7,12 @@ "description": "Display a list of all pages.", "keywords": [ "menu", "navigation" ], "textdomain": "default", - "attributes": {}, + "attributes": { + "rootPageID": { + "type": "integer", + "default": 0 + } + }, "usesContext": [ "textColor", "customTextColor", diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js index 71cbfa437a1f9..ac7ed6162cf97 100644 --- a/packages/block-library/src/page-list/edit.js +++ b/packages/block-library/src/page-list/edit.js @@ -7,11 +7,18 @@ import classnames from 'classnames'; * WordPress dependencies */ import { + InspectorControls, BlockControls, useBlockProps, getColorClassName, } from '@wordpress/block-editor'; -import { ToolbarButton, Spinner, Notice } from '@wordpress/components'; +import { + PanelBody, + ToolbarButton, + Spinner, + Notice, + ComboboxControl, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useMemo, useState, memo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; @@ -27,7 +34,13 @@ import { ItemSubmenuIcon } from '../navigation-link/icons'; // Performance of Navigation Links is not good past this value. const MAX_PAGE_COUNT = 100; -export default function PageListEdit( { context, clientId } ) { +export default function PageListEdit( { + context, + clientId, + attributes, + setAttributes, +} ) { + const { parentPageID } = attributes; const { pagesByParentId, totalPages, hasResolvedPages } = usePageData(); const isNavigationChild = 'showSubmenuIcon' in context; @@ -86,6 +99,7 @@ export default function PageListEdit( { context, clientId } ) {
    @@ -93,8 +107,35 @@ export default function PageListEdit( { context, clientId } ) { } }; + const useParentOptions = () => { + const [ pages ] = useGetPages(); + return pages?.reduce( ( accumulator, page ) => { + accumulator.push( { + value: page.id, + label: page.title.rendered, + } ); + return accumulator; + }, [] ); + }; + return ( <> + + + + setAttributes( { parentPageID: value ?? 0 } ) + } + help={ __( + 'Choose a page to show only its subpages.' + ) } + /> + + { allowConvertToLinks && ( @@ -129,7 +170,7 @@ function useFrontPageId() { }, [] ); } -function usePageData() { +function useGetPages() { const { records: pages, hasResolved: hasResolvedPages } = useEntityRecords( 'postType', 'page', @@ -142,10 +183,21 @@ function usePageData() { } ); + return [ pages, hasResolvedPages ]; +} + +function usePageData( pageId = 0 ) { + const [ pages, hasResolvedPages ] = useGetPages(); + return useMemo( () => { // TODO: Once the REST API supports passing multiple values to // 'orderby', this can be removed. // https://core.trac.wordpress.org/ticket/39037 + + if ( pageId !== 0 ) { + return pages.find( ( page ) => page.id === pageId ); + } + const sortedPages = [ ...( pages ?? [] ) ].sort( ( a, b ) => { if ( a.menu_order === b.menu_order ) { return a.title.rendered.localeCompare( b.title.rendered ); @@ -167,7 +219,7 @@ function usePageData() { hasResolvedPages, totalPages: pages?.length ?? null, }; - }, [ pages, hasResolvedPages ] ); + }, [ pageId, pages, hasResolvedPages ] ); } const PageItems = memo( function PageItems( { @@ -176,7 +228,10 @@ const PageItems = memo( function PageItems( { parentId = 0, depth = 0, } ) { - const pages = pagesByParentId.get( parentId ); + const parentPage = usePageData( parentId ); + const pages = pagesByParentId.get( parentId ) + ? pagesByParentId.get( parentId ) + : [ parentPage ]; const frontPageId = useFrontPageId(); if ( ! pages?.length ) { diff --git a/packages/block-library/src/page-list/index.php b/packages/block-library/src/page-list/index.php index 32acdad45ed63..7944f6f3a84ad 100644 --- a/packages/block-library/src/page-list/index.php +++ b/packages/block-library/src/page-list/index.php @@ -252,6 +252,8 @@ function render_block_core_page_list( $attributes, $content, $block ) { static $block_id = 0; ++$block_id; + $parent_page_id = $attributes['parentPageID']; + $all_pages = get_pages( array( 'sort_column' => 'menu_order,post_title', @@ -306,6 +308,13 @@ function render_block_core_page_list( $attributes, $content, $block ) { $nested_pages = block_core_page_list_nest_pages( $top_level_pages, $pages_with_children ); + if ( 0 !== $parent_page_id ) { + $nested_pages = block_core_page_list_nest_pages( + $pages_with_children[ $parent_page_id ], + $pages_with_children + ); + } + $is_navigation_child = array_key_exists( 'showSubmenuIcon', $block->context ); $open_submenus_on_click = array_key_exists( 'openSubmenusOnClick', $block->context ) ? $block->context['openSubmenusOnClick'] : false; diff --git a/test/integration/fixtures/blocks/core__page-list.json b/test/integration/fixtures/blocks/core__page-list.json index 0bb67d8e44efd..52e22aa9059ab 100644 --- a/test/integration/fixtures/blocks/core__page-list.json +++ b/test/integration/fixtures/blocks/core__page-list.json @@ -2,7 +2,9 @@ { "name": "core/page-list", "isValid": true, - "attributes": {}, + "attributes": { + "rootPageID": 0 + }, "innerBlocks": [] } ] From 20cc4aa86e3120449bad0bbf39e5bfb792376da5 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:27:39 +0200 Subject: [PATCH 079/380] Lodash: Simplify a few isEmpty calls (#45525) --- packages/block-library/src/audio/edit.native.js | 3 +-- packages/block-library/src/embed/embed-preview.native.js | 3 +-- packages/block-library/src/gallery/gallery.native.js | 3 +-- packages/block-library/src/gallery/v1/gallery.native.js | 3 +-- packages/block-library/src/video/edit.native.js | 3 +-- .../components/src/mobile/bottom-sheet/switch-cell.native.js | 4 ++-- packages/editor/src/components/post-title/index.native.js | 5 ++--- 7 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/block-library/src/audio/edit.native.js b/packages/block-library/src/audio/edit.native.js index 30ee44ff3f948..fbc39ba190d3e 100644 --- a/packages/block-library/src/audio/edit.native.js +++ b/packages/block-library/src/audio/edit.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { TouchableWithoutFeedback } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -228,7 +227,7 @@ function AudioEdit( { - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty Audio caption. */ __( 'Audio caption. Empty' ) : sprintf( diff --git a/packages/block-library/src/embed/embed-preview.native.js b/packages/block-library/src/embed/embed-preview.native.js index bb8df663baf28..87abc38348d56 100644 --- a/packages/block-library/src/embed/embed-preview.native.js +++ b/packages/block-library/src/embed/embed-preview.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { TouchableWithoutFeedback } from 'react-native'; -import { isEmpty } from 'lodash'; import classnames from 'classnames/dedupe'; /** @@ -52,7 +51,7 @@ const EmbedPreview = ( { styles[ `embed-preview__sandbox--align-${ align }` ]; function accessibilityLabelCreator( caption ) { - return isEmpty( caption ) + return ! caption ? /* translators: accessibility text. Empty Embed caption. */ __( 'Embed caption. Empty' ) : sprintf( diff --git a/packages/block-library/src/gallery/gallery.native.js b/packages/block-library/src/gallery/gallery.native.js index 16c504f6ccc19..a5174d0215c42 100644 --- a/packages/block-library/src/gallery/gallery.native.js +++ b/packages/block-library/src/gallery/gallery.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * Internal dependencies @@ -100,7 +99,7 @@ export const Gallery = ( props ) => { isSelected={ isCaptionSelected } accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty gallery caption. */ 'Gallery caption. Empty' diff --git a/packages/block-library/src/gallery/v1/gallery.native.js b/packages/block-library/src/gallery/v1/gallery.native.js index 7908d17988a1a..c1d13cb6313e0 100644 --- a/packages/block-library/src/gallery/v1/gallery.native.js +++ b/packages/block-library/src/gallery/v1/gallery.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * Internal dependencies @@ -142,7 +141,7 @@ export const Gallery = ( props ) => { isSelected={ isCaptionSelected } accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty gallery caption. */ 'Gallery caption. Empty' : sprintf( diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index fb4937ce5da73..99225ba5d5c56 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View, TouchableWithoutFeedback, Text } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -367,7 +366,7 @@ class VideoEdit extends Component { - isEmpty( caption ) + ! caption ? /* translators: accessibility text. Empty video caption. */ __( 'Video caption. Empty' ) : sprintf( diff --git a/packages/components/src/mobile/bottom-sheet/switch-cell.native.js b/packages/components/src/mobile/bottom-sheet/switch-cell.native.js index b49f03902ddb8..9dcaf794df750 100644 --- a/packages/components/src/mobile/bottom-sheet/switch-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/switch-cell.native.js @@ -2,7 +2,7 @@ * External dependencies */ import { Switch } from 'react-native'; -import { isEmpty } from 'lodash'; + /** * WordPress dependencies */ @@ -20,7 +20,7 @@ export default function BottomSheetSwitchCell( props ) { }; const getAccessibilityLabel = () => { - if ( isEmpty( cellProps.help ) ) { + if ( ! cellProps.help ) { return value ? sprintf( /* translators: accessibility text. Switch setting ON state. %s: Switch title. */ diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 0d1d87c81e417..4da9d8c308110 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -80,7 +79,7 @@ class PostTitle extends Component { getTitle( title, postType ) { if ( 'page' === postType ) { - return isEmpty( title ) + return ! title ? /* translators: accessibility text. empty page title. */ __( 'Page title. Empty' ) : sprintf( @@ -90,7 +89,7 @@ class PostTitle extends Component { ); } - return isEmpty( title ) + return ! title ? /* translators: accessibility text. empty post title. */ __( 'Post title. Empty' ) : sprintf( From a7be92761e4707fdf9de6fd5337862d82af02133 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:30:08 +0200 Subject: [PATCH 080/380] Lodash: Refactor away from `_.pick()` in block library (#45940) * Lodash: Refactor away from _.pick() in block library * Add missing empty section check to updateSelectedCell() * Revert "Add missing empty section check to updateSelectedCell()" This reverts commit 9e9bea5766d801d82a8b48cd5c57a45630433721. * Improve precision of approach * Null-proofing --- packages/block-library/src/gallery/shared.js | 9 ++++-- .../block-library/src/gallery/v1/shared.js | 9 ++++-- packages/block-library/src/image/edit.js | 9 ++++-- packages/block-library/src/image/image.js | 18 +++++++----- packages/block-library/src/site-logo/edit.js | 13 +++++++-- packages/block-library/src/table/state.js | 29 ++++++++++++++----- 6 files changed, 64 insertions(+), 23 deletions(-) diff --git a/packages/block-library/src/gallery/shared.js b/packages/block-library/src/gallery/shared.js index 4765b4395994f..f3142bbbaf9da 100644 --- a/packages/block-library/src/gallery/shared.js +++ b/packages/block-library/src/gallery/shared.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, pick } from 'lodash'; +import { get } from 'lodash'; /** * WordPress dependencies @@ -13,7 +13,12 @@ export function defaultColumnsNumber( imageCount ) { } export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', sizeSlug, 'url' ] ) || get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ) || diff --git a/packages/block-library/src/gallery/v1/shared.js b/packages/block-library/src/gallery/v1/shared.js index 484020cb9d58c..9a0957cc7a120 100644 --- a/packages/block-library/src/gallery/v1/shared.js +++ b/packages/block-library/src/gallery/v1/shared.js @@ -1,10 +1,15 @@ /** * External dependencies */ -import { get, pick } from 'lodash'; +import { get } from 'lodash'; export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link', 'caption' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', sizeSlug, 'url' ] ) || get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ) || diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index baba2cee24ec3..4e504896b4709 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { get, isEmpty, pick } from 'lodash'; +import { get, isEmpty } from 'lodash'; /** * WordPress dependencies @@ -58,7 +58,12 @@ import { } from './constants'; export const pickRelevantMediaFiles = ( image, size ) => { - const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); + const imageProps = Object.fromEntries( + Object.entries( image ?? {} ).filter( ( [ key ] ) => + [ 'alt', 'id', 'link', 'caption' ].includes( key ) + ) + ); + imageProps.url = get( image, [ 'sizes', size, 'url' ] ) || get( image, [ 'media_details', 'sizes', size, 'source_url' ] ) || diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 7245ce232c64b..1e4881181701b 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, filter, isEmpty, map, pick } from 'lodash'; +import { get, filter, isEmpty, map } from 'lodash'; /** * WordPress dependencies @@ -135,12 +135,16 @@ export default function Image( { } = select( blockEditorStore ); const rootClientId = getBlockRootClientId( clientId ); - const settings = pick( getSettings(), [ - 'imageEditing', - 'imageSizes', - 'maxWidth', - 'mediaUpload', - ] ); + const settings = Object.fromEntries( + Object.entries( getSettings() ).filter( ( [ key ] ) => + [ + 'imageEditing', + 'imageSizes', + 'maxWidth', + 'mediaUpload', + ].includes( key ) + ) + ); return { ...settings, diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index 8b85798baee71..0d752a5dbae31 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import { pick } from 'lodash'; /** * WordPress dependencies @@ -87,7 +86,11 @@ const SiteLogo = ( { ); return { title: siteEntities?.name, - ...pick( getSettings(), [ 'imageEditing', 'maxWidth' ] ), + ...Object.fromEntries( + Object.entries( getSettings() ).filter( ( [ key ] ) => + [ 'imageEditing', 'maxWidth' ].includes( key ) + ) + ), }; }, [] ); @@ -121,7 +124,11 @@ const SiteLogo = ( { alt={ alt } onLoad={ ( event ) => { setNaturalSize( - pick( event.target, [ 'naturalWidth', 'naturalHeight' ] ) + Object.fromEntries( + Object.entries( event.target ).filter( ( [ key ] ) => + [ 'naturalWidth', 'naturalHeight' ].includes( key ) + ) + ) ); } } /> diff --git a/packages/block-library/src/table/state.js b/packages/block-library/src/table/state.js index 68fee2d0bda9b..a1aafc3145936 100644 --- a/packages/block-library/src/table/state.js +++ b/packages/block-library/src/table/state.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get, mapValues, pick } from 'lodash'; +import { get, mapValues } from 'lodash'; const INHERITED_COLUMN_ATTRIBUTES = [ 'align' ]; @@ -78,7 +78,11 @@ export function updateSelectedCell( state, selection, updateCell ) { return state; } - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); const { sectionName: selectionSectionName, rowIndex: selectionRowIndex } = selection; @@ -174,9 +178,12 @@ export function insertRow( state, { sectionName, rowIndex, columnCount } ) { [ 'cells', index ], {} ); - const inheritedAttributes = pick( - firstCellInColumn, - INHERITED_COLUMN_ATTRIBUTES + + const inheritedAttributes = Object.fromEntries( + Object.entries( firstCellInColumn ).filter( + ( [ key ] ) => + INHERITED_COLUMN_ATTRIBUTES.includes( key ) + ) ); return { @@ -220,7 +227,11 @@ export function deleteRow( state, { sectionName, rowIndex } ) { * @return {Object} New table state. */ export function insertColumn( state, { columnIndex } ) { - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); return mapValues( tableSections, ( section, sectionName ) => { // Bail early if the table section is empty. @@ -259,7 +270,11 @@ export function insertColumn( state, { columnIndex } ) { * @return {Object} New table state. */ export function deleteColumn( state, { columnIndex } ) { - const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); + const tableSections = Object.fromEntries( + Object.entries( state ).filter( ( [ key ] ) => + [ 'head', 'body', 'foot' ].includes( key ) + ) + ); return mapValues( tableSections, ( section ) => { // Bail early if the table section is empty. From 4c21122dab619ece1678bdfb41b1fc08304028df Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 22 Nov 2022 20:53:24 +1100 Subject: [PATCH 081/380] Style engine: trim multiple selector strings (#45873) * Trim multiple selector string, that is a string of selectors separated by a comma, passed to the style engine. This is so we can correctly align prettified content, and remove spaces between multiple selectors with the same styles. * Only remove selector white spaces when $should_prettify is true * Updated test --- .../class-wp-style-engine-css-rule.php | 6 +++-- .../class-wp-style-engine-css-rule-test.php | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/style-engine/class-wp-style-engine-css-rule.php b/packages/style-engine/class-wp-style-engine-css-rule.php index e160551757331..76ba12673aeda 100644 --- a/packages/style-engine/class-wp-style-engine-css-rule.php +++ b/packages/style-engine/class-wp-style-engine-css-rule.php @@ -113,8 +113,10 @@ public function get_css( $should_prettify = false, $indent_count = 0 ) { $declarations_indent = $should_prettify ? $indent_count + 1 : 0; $suffix = $should_prettify ? "\n" : ''; $spacer = $should_prettify ? ' ' : ''; - $selector = $should_prettify ? str_replace( ',', ",\n", $this->get_selector() ) : $this->get_selector(); - $css_declarations = $this->declarations->get_declarations_string( $should_prettify, $declarations_indent ); + // Trims any multiple selectors strings. + $selector = $should_prettify ? implode( ',', array_map( 'trim', explode( ',', $this->get_selector() ) ) ) : $this->get_selector(); + $selector = $should_prettify ? str_replace( array( ',' ), ",\n", $selector ) : $selector; + $css_declarations = $this->declarations->get_declarations_string( $should_prettify, $declarations_indent ); if ( empty( $css_declarations ) ) { return ''; diff --git a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php index 02482c70e4d84..444c83f150f91 100644 --- a/phpunit/style-engine/class-wp-style-engine-css-rule-test.php +++ b/phpunit/style-engine/class-wp-style-engine-css-rule-test.php @@ -142,4 +142,31 @@ public function test_should_prettify_css_rule_output() { $this->assertSame( $expected, $css_rule->get_css( true ) ); } + + /** + * Tests that a string of multiple selectors is trimmed. + * + * @covers ::get_css + */ + public function test_should_trim_multiple_selectors() { + $selector = '.poirot, .poirot:active, #miss-marple > .st-mary-mead '; + $input_declarations = array( + 'margin-left' => '0', + 'font-family' => 'Detective Sans', + ); + $css_declarations = new WP_Style_Engine_CSS_Declarations_Gutenberg( $input_declarations ); + $css_rule = new WP_Style_Engine_CSS_Rule_Gutenberg( $selector, $css_declarations ); + $expected = '.poirot, .poirot:active, #miss-marple > .st-mary-mead {margin-left:0;font-family:Detective Sans;}'; + + $this->assertSame( $expected, $css_rule->get_css(), 'Return value should be not prettified.' ); + + $expected_prettified = '.poirot, +.poirot:active, +#miss-marple > .st-mary-mead { + margin-left: 0; + font-family: Detective Sans; +}'; + + $this->assertSame( $expected_prettified, $css_rule->get_css( true ), 'Return value should be prettified with new lines and indents.' ); + } } From 516d05c1b370665e44b153be32a8f96d6ba902df Mon Sep 17 00:00:00 2001 From: Artemio Morales Date: Tue, 22 Nov 2022 05:13:29 -0500 Subject: [PATCH 082/380] Clarify explanation of how 'Convert to Links' works in Page List block (#45394) * Clarify explanation of how 'Convert to Links' works * Remove splitting of translated string * Update string to use createInterpolateElement to ensure proper translation * Simplify and clarify language in Page List edit modal * Remove tip from modal copy --- .../src/page-list/convert-to-links-modal.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/block-library/src/page-list/convert-to-links-modal.js b/packages/block-library/src/page-list/convert-to-links-modal.js index bbb3e1e44e98b..826539ec37834 100644 --- a/packages/block-library/src/page-list/convert-to-links-modal.js +++ b/packages/block-library/src/page-list/convert-to-links-modal.js @@ -91,18 +91,13 @@ export default function ConvertToLinksModal( { onClose, clientId } ) {

    { __( - 'To edit this navigation menu, convert it to single page links. This allows you to add, re-order, remove items, or edit their labels.' - ) } -

    -

    - { __( - "Note: if you add new pages to your site, you'll need to add them to your navigation menu." + 'This menu is automatically kept in sync with pages on your site. You can manage the menu yourself by clicking customize below.' ) }

    @@ -119,7 +114,7 @@ export default function ConvertToLinksModal( { onClose, clientId } ) { createBlock: create, } ) } > - { __( 'Convert' ) } + { __( 'Customize' ) }
    From 6cedad20b0d96ac650b17d37b6071a5d7dbad881 Mon Sep 17 00:00:00 2001 From: Maggie Date: Tue, 22 Nov 2022 11:55:52 +0100 Subject: [PATCH 083/380] Include offcanvas specific styles (#45963) Co-authored-by: Dave Smith --- .../components/off-canvas-editor/style.scss | 398 +----------------- packages/block-editor/src/style.scss | 3 + 2 files changed, 4 insertions(+), 397 deletions(-) diff --git a/packages/block-editor/src/components/off-canvas-editor/style.scss b/packages/block-editor/src/components/off-canvas-editor/style.scss index 9782dc7027bc7..7f22f6f5340cf 100644 --- a/packages/block-editor/src/components/off-canvas-editor/style.scss +++ b/packages/block-editor/src/components/off-canvas-editor/style.scss @@ -1,397 +1 @@ -.block-editor-list-view-tree { - width: 100%; - border-collapse: collapse; - padding: 0; - margin: 0; - - // Move upwards when in modal. - .components-modal__content & { - margin: (-$grid-unit-15) (-$grid-unit-15 * 0.5) 0; - width: calc(100% + #{ $grid-unit-15 }); - } -} - -.block-editor-list-view-leaf { - // Use position relative for row animation. - position: relative; - - // The background has to be applied to the td, not tr, or border-radius won't work. - &.is-selected td { - background: var(--wp-admin-theme-color); - } - &.is-selected .block-editor-list-view-block-contents, - &.is-selected .components-button.has-icon { - color: $white; - } - &.is-selected .block-editor-list-view-block-contents { - // Hide selection styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - background: none; - color: $gray-900; - } - } - &.is-selected .block-editor-list-view-block-contents:focus { - &::after { - box-shadow: - inset 0 0 0 1px $white, - 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - } - } - &.is-selected .block-editor-list-view-block__menu:focus { - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) $white; - } - - &.is-dragging { - display: none; - } - - // Border radius for corners of the selected item. - &.is-first-selected td:first-child { - border-top-left-radius: $radius-block-ui; - } - &.is-first-selected td:last-child { - border-top-right-radius: $radius-block-ui; - } - &.is-last-selected td:first-child { - border-bottom-left-radius: $radius-block-ui; - } - &.is-last-selected td:last-child { - border-bottom-right-radius: $radius-block-ui; - } - &.is-branch-selected:not(.is-selected) { - // Lighten a CSS variable without introducing a new SASS variable - background: - linear-gradient(transparentize($white, 0.1), transparentize($white, 0.1)), - linear-gradient(var(--wp-admin-theme-color), var(--wp-admin-theme-color)); - } - &.is-branch-selected.is-first-selected td:first-child { - border-top-left-radius: $radius-block-ui; - } - &.is-branch-selected.is-first-selected td:last-child { - border-top-right-radius: $radius-block-ui; - } - &[aria-expanded="false"] { - &.is-branch-selected.is-first-selected td:first-child { - border-top-left-radius: $radius-block-ui; - } - &.is-branch-selected.is-first-selected td:last-child { - border-top-right-radius: $radius-block-ui; - } - &.is-branch-selected.is-last-selected td:first-child { - border-bottom-left-radius: $radius-block-ui; - } - &.is-branch-selected.is-last-selected td:last-child { - border-bottom-right-radius: $radius-block-ui; - } - } - &.is-branch-selected:not(.is-selected) td { - border-radius: 0; - } - - - // List View renders a fixed number of items and relies on each item having a fixed 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. - .block-editor-list-view-block-contents { - display: flex; - align-items: center; - width: 100%; - height: auto; - padding: ($grid-unit-15 * 0.5) $grid-unit-05 ($grid-unit-15 * 0.5) 0; - text-align: left; - color: $gray-900; - border-radius: $radius-block-ui; - position: relative; - white-space: nowrap; - - &.is-dropping-before::before { - content: ""; - position: absolute; - pointer-events: none; - transition: border-color 0.1s linear, border-style 0.1s linear, box-shadow 0.1s linear; - top: -2px; - right: 0; - left: 0; - border-top: 4px solid var(--wp-admin-theme-color); - } - - .components-modal__content & { - padding-left: 0; - padding-right: 0; - } - } - - .block-editor-list-view-block-contents:focus { - box-shadow: none; - - &::after { - content: ""; - position: absolute; - top: 0; - right: -(24px + 5px); // Icon size + padding. - bottom: 0; - left: 0; - border-radius: inherit; - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - z-index: 2; - pointer-events: none; - - // Hide focus styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - box-shadow: none; - } - } - } - // Fix focus styling width when one row has fewer cells. - &.has-single-cell .block-editor-list-view-block-contents:focus::after { - right: 0; - } - - .block-editor-list-view-block__menu:focus { - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - z-index: 1; - - // Hide focus styles while a user is dragging blocks/files etc. - .is-dragging-components-draggable & { - box-shadow: none; - } - } - - &.is-visible .block-editor-list-view-block-contents { - opacity: 1; - @include edit-post__fade-in-animation; - } - - .block-editor-block-icon { - align-self: flex-start; - margin-right: $grid-unit-10; - width: $icon-size; - } - - .block-editor-list-view-block__menu-cell, - .block-editor-list-view-block__mover-cell, - .block-editor-list-view-block__contents-cell { - padding-top: 0; - padding-bottom: 0; - } - - .block-editor-list-view-block__menu-cell, - .block-editor-list-view-block__mover-cell { - line-height: 0; - width: $button-size; - vertical-align: middle; - @include reduce-motion("transition"); - - > * { - opacity: 0; - } - - // Show on hover, visible, and show above to keep the hit area size. - &:hover, - &.is-visible { - position: relative; - z-index: 1; - - > * { - opacity: 1; - @include edit-post__fade-in-animation; - } - } - - &, - .components-button.has-icon { - width: 24px; - min-width: 24px; - padding: 0; - } - } - - .block-editor-list-view-block__menu-cell { - padding-right: $grid-unit-05; - - .components-button.has-icon { - height: 24px; - } - } - - .block-editor-list-view-block__mover-cell-alignment-wrapper { - display: flex; - height: 100%; - flex-direction: column; - align-items: center; - } - - // Keep the tap target large but the focus target small. - .block-editor-block-mover-button { - position: relative; - width: $button-size; - height: $button-size-small; - - // Position the icon. - svg { - position: relative; - height: $button-size-small; - } - - &.is-up-button { - margin-top: -$grid-unit-15 * 0.5; - align-items: flex-end; - svg { - bottom: -$grid-unit-05; - } - } - - &.is-down-button { - margin-bottom: -$grid-unit-15 * 0.5; - align-items: flex-start; - svg { - top: -$grid-unit-05; - } - } - - // Tweak size and position of focus ring. - &::before { - height: 16px; - min-width: 100%; - left: 0; - right: 0; - } - } - - .block-editor-inserter__toggle { - background: $gray-900; - color: $white; - height: $grid-unit-30; - margin: 6px 6px 6px 1px; - min-width: $grid-unit-30; - - &:active { - color: $white; - } - } - - .block-editor-list-view-block-select-button__label-wrapper { - min-width: 120px; - } - - .block-editor-list-view-block-select-button__title { - flex: 1; - position: relative; - - .components-truncate { - position: absolute; - width: 100%; - transform: translateY(-50%); - } - } - - .block-editor-list-view-block-select-button__anchor-wrapper { - position: relative; - max-width: min(110px, 40%); - width: 100%; - } - - .block-editor-list-view-block-select-button__anchor { - position: absolute; - right: 0; - transform: translateY(-50%); - background: rgba($black, 0.1); - border-radius: $radius-block-ui; - padding: 2px 6px; - max-width: 100%; - box-sizing: border-box; - } - - &.is-selected .block-editor-list-view-block-select-button__anchor { - background: rgba($black, 0.3); - } - - .block-editor-list-view-block-select-button__lock { - line-height: 0; - width: 24px; - min-width: 24px; - margin-left: auto; - padding: 0; - vertical-align: middle; - } -} - -.block-editor-list-view-block-select-button__description, -.block-editor-list-view-appender__description { - display: none; -} - -.block-editor-list-view-block__contents-cell, -.block-editor-list-view-appender__cell { - .block-editor-list-view-block__contents-container, - .block-editor-list-view-appender__container { - display: flex; - } -} - -// Chevron container metrics. -.block-editor-list-view__expander { - height: $icon-size; - margin-left: $grid-unit-05; - width: $icon-size; -} - -// First level of indentation is aria-level 2, max indent is 8. -// Indent is a full icon size, plus 4px which optically aligns child icons to the text label above. -$block-navigation-max-indent: 8; -.block-editor-list-view-leaf[aria-level] .block-editor-list-view__expander { - margin-left: ( $icon-size ) * $block-navigation-max-indent + 4 * ( $block-navigation-max-indent - 1 ); -} - -.block-editor-list-view-leaf:not([aria-level="1"]) { - .block-editor-list-view__expander { - margin-right: 4px; - } -} - -@for $i from 0 to $block-navigation-max-indent { - .block-editor-list-view-leaf[aria-level="#{ $i + 1 }"] .block-editor-list-view__expander { - @if $i - 1 >= 0 { - margin-left: ( $icon-size * $i ) + 4 * ($i - 1); - } - @else { - margin-left: ( $icon-size * $i ); - } - } -} - -.block-editor-list-view-leaf .block-editor-list-view__expander { - visibility: hidden; -} - -// Point downwards when open. -.block-editor-list-view-leaf[aria-expanded="true"] .block-editor-list-view__expander svg { - visibility: visible; - transition: transform 0.2s ease; - transform: rotate(90deg); - @include reduce-motion("transition"); -} - -// Point rightwards when closed -.block-editor-list-view-leaf[aria-expanded="false"] .block-editor-list-view__expander svg { - visibility: visible; - transform: rotate(0deg); - transition: transform 0.2s ease; - @include reduce-motion("transition"); -} - -.block-editor-list-view-drop-indicator { - pointer-events: none; - - .block-editor-list-view-drop-indicator__line { - background: var(--wp-admin-theme-color); - height: $border-width; - } -} - -.block-editor-list-view-placeholder { - padding: 0; - margin: 0; - height: 36px; -} - +//Styles for off-canvas editor, remove this line when you add some css to this file! diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 853a030f04594..0073742361158 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -65,4 +65,7 @@ @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 821db1c8fc598a8dd69b52a97fe843f691b5cfa4 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 22 Nov 2022 11:06:19 +0000 Subject: [PATCH 084/380] Navigation: Add label field to navigation link and navigation submenu (#45964) --- packages/block-library/src/navigation-link/edit.js | 8 ++++++++ packages/block-library/src/navigation-submenu/edit.js | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 30a2f9ebb4b52..0c08209130ba4 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -737,6 +737,14 @@ export default function NavigationLinkEdit( { { /* Warning, this duplicated in packages/block-library/src/navigation-submenu/edit.js */ } + { + setAttributes( { label: labelValue } ); + } } + label={ __( 'Label' ) } + autoComplete="off" + /> { diff --git a/packages/block-library/src/navigation-submenu/edit.js b/packages/block-library/src/navigation-submenu/edit.js index 9810c7ebab05c..aa508e63a6226 100644 --- a/packages/block-library/src/navigation-submenu/edit.js +++ b/packages/block-library/src/navigation-submenu/edit.js @@ -578,6 +578,14 @@ export default function NavigationSubmenuEdit( { { /* Warning, this duplicated in packages/block-library/src/navigation-link/edit.js */ } + { + setAttributes( { label: labelValue } ); + } } + label={ __( 'Label' ) } + autoComplete="off" + /> { From 367c180b384f6656aa6fa5f6926e0c39ab073905 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 22 Nov 2022 13:02:41 +0000 Subject: [PATCH 085/380] Page List: If no parent page is set, still render all children (#45967) * Page List: If no parent page is set, still render all children * Fixes the block not rendering with no parent by correctly renaming the new parentPageID attribute. Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> * regenerated fixtures for new attribute name Co-authored-by: Andrei Draganescu Co-authored-by: Ben Dwyer <275961+scruffian@users.noreply.github.com> --- docs/reference-guides/core-blocks.md | 2 +- packages/block-library/src/page-list/block.json | 2 +- test/integration/fixtures/blocks/core__page-list.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 8650c9d7d3274..09e48ebda0f65 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -429,7 +429,7 @@ Display a list of all pages. ([Source](https://github.com/WordPress/gutenberg/tr - **Name:** core/page-list - **Category:** widgets - **Supports:** ~~html~~, ~~reusable~~ -- **Attributes:** rootPageID +- **Attributes:** parentPageID ## Paragraph diff --git a/packages/block-library/src/page-list/block.json b/packages/block-library/src/page-list/block.json index 144cecb428396..2fc6993849d6f 100644 --- a/packages/block-library/src/page-list/block.json +++ b/packages/block-library/src/page-list/block.json @@ -8,7 +8,7 @@ "keywords": [ "menu", "navigation" ], "textdomain": "default", "attributes": { - "rootPageID": { + "parentPageID": { "type": "integer", "default": 0 } diff --git a/test/integration/fixtures/blocks/core__page-list.json b/test/integration/fixtures/blocks/core__page-list.json index 52e22aa9059ab..b1992a437b884 100644 --- a/test/integration/fixtures/blocks/core__page-list.json +++ b/test/integration/fixtures/blocks/core__page-list.json @@ -3,7 +3,7 @@ "name": "core/page-list", "isValid": true, "attributes": { - "rootPageID": 0 + "parentPageID": 0 }, "innerBlocks": [] } From d7e3cc12afac33be0b5b6eb9c85a27506cab4e5d Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:36:31 +0200 Subject: [PATCH 086/380] Lodash: Refactor editor away from _.pick() (#45944) --- .../provider/use-block-editor-settings.js | 97 +++++++++---------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 51d16ef1b6a7d..0b69dcc66eb81 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { pick } from 'lodash'; - /** * WordPress dependencies */ @@ -133,50 +128,54 @@ function useBlockEditorSettings( settings, hasTemplate ) { return useMemo( () => ( { - ...pick( settings, [ - '__experimentalBlockDirectory', - '__experimentalDiscussionSettings', - '__experimentalFeatures', - '__experimentalPreferredStyleVariations', - '__experimentalSetIsInserterOpened', - '__unstableGalleryWithImageBlocks', - 'alignWide', - 'allowedBlockTypes', - 'bodyPlaceholder', - 'canLockBlocks', - 'codeEditingEnabled', - 'colors', - 'disableCustomColors', - 'disableCustomFontSizes', - 'disableCustomSpacingSizes', - 'disableCustomGradients', - 'disableLayoutStyles', - 'enableCustomLineHeight', - 'enableCustomSpacing', - 'enableCustomUnits', - 'focusMode', - 'fontSizes', - 'gradients', - 'generateAnchors', - 'hasFixedToolbar', - 'isDistractionFree', - 'hasInlineToolbar', - 'imageDefaultSize', - 'imageDimensions', - 'imageEditing', - 'imageSizes', - 'isRTL', - 'keepCaretInsideBlock', - 'maxWidth', - 'onUpdateDefaultBlockStyles', - 'styles', - 'template', - 'templateLock', - 'titlePlaceholder', - 'supportsLayout', - 'widgetTypesToHideFromLegacyWidgetBlock', - '__unstableResolvedAssets', - ] ), + ...Object.fromEntries( + Object.entries( settings ).filter( ( [ key ] ) => + [ + '__experimentalBlockDirectory', + '__experimentalDiscussionSettings', + '__experimentalFeatures', + '__experimentalPreferredStyleVariations', + '__experimentalSetIsInserterOpened', + '__unstableGalleryWithImageBlocks', + 'alignWide', + 'allowedBlockTypes', + 'bodyPlaceholder', + 'canLockBlocks', + 'codeEditingEnabled', + 'colors', + 'disableCustomColors', + 'disableCustomFontSizes', + 'disableCustomSpacingSizes', + 'disableCustomGradients', + 'disableLayoutStyles', + 'enableCustomLineHeight', + 'enableCustomSpacing', + 'enableCustomUnits', + 'focusMode', + 'fontSizes', + 'gradients', + 'generateAnchors', + 'hasFixedToolbar', + 'isDistractionFree', + 'hasInlineToolbar', + 'imageDefaultSize', + 'imageDimensions', + 'imageEditing', + 'imageSizes', + 'isRTL', + 'keepCaretInsideBlock', + 'maxWidth', + 'onUpdateDefaultBlockStyles', + 'styles', + 'template', + 'templateLock', + 'titlePlaceholder', + 'supportsLayout', + 'widgetTypesToHideFromLegacyWidgetBlock', + '__unstableResolvedAssets', + ].includes( key ) + ) + ), mediaUpload: hasUploadPermissions ? mediaUpload : undefined, __experimentalReusableBlocks: reusableBlocks, __experimentalBlockPatterns: blockPatterns, From c75fc6d4b8f09dfc4fb2286814cbccc737ccc40f Mon Sep 17 00:00:00 2001 From: Miguel Torres Date: Tue, 22 Nov 2022 17:19:07 +0100 Subject: [PATCH 087/380] Update `gutenberg_get_global_stylesheet` to use `WP_Object_Cache` (#45679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André <583546+oandregal@users.noreply.github.com> --- .../get-global-styles-and-settings.php | 85 -------------- .../class-wp-theme-json-resolver-6-2.php | 25 ++++ lib/compat/wordpress-6.2/default-filters.php | 10 ++ .../get-global-styles-and-settings.php | 110 ++++++++++++++++++ phpunit/wp-get-global-stylesheet-test.php | 98 ++++++++++++++++ 5 files changed, 243 insertions(+), 85 deletions(-) create mode 100644 phpunit/wp-get-global-stylesheet-test.php diff --git a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php index 35c540ce1c57a..fd6113c7405c4 100644 --- a/lib/compat/wordpress-6.1/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.1/get-global-styles-and-settings.php @@ -54,88 +54,3 @@ function ( $item ) { } } } - -/** - * Returns the stylesheet resulting of merging core, theme, and user data. - * - * @param array $types Types of styles to load. Optional. - * It accepts 'variables', 'styles', 'presets' as values. - * If empty, it'll load all for themes with theme.json support - * and only [ 'variables', 'presets' ] for themes without theme.json support. - * - * @return string Stylesheet. - */ -function gutenberg_get_global_stylesheet( $types = array() ) { - // Return cached value if it can be used and exists. - // It's cached by theme to make sure that theme switching clears the cache. - $can_use_cached = ( - ( empty( $types ) ) && - ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && - ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) && - ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && - ! is_admin() - ); - $transient_name = 'gutenberg_global_styles_' . get_stylesheet(); - if ( $can_use_cached ) { - $cached = get_transient( $transient_name ); - if ( $cached ) { - return $cached; - } - } - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); - $supports_theme_json = wp_theme_has_theme_json(); - if ( empty( $types ) && ! $supports_theme_json ) { - $types = array( 'variables', 'presets', 'base-layout-styles' ); - } elseif ( empty( $types ) ) { - $types = array( 'variables', 'styles', 'presets' ); - } - - /* - * If variables are part of the stylesheet, - * we add them. - * - * This is so themes without a theme.json still work as before 5.9: - * they can override the default presets. - * See https://core.trac.wordpress.org/ticket/54782 - */ - $styles_variables = ''; - if ( in_array( 'variables', $types, true ) ) { - /* - * We only use the default, theme, and custom origins. - * This is because styles for blocks origin are added - * at a later phase (render cycle) so we only render the ones in use. - * @see wp_add_global_styles_for_blocks - */ - $origins = array( 'default', 'theme', 'custom' ); - $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins ); - $types = array_diff( $types, array( 'variables' ) ); - } - - /* - * For the remaining types (presets, styles), we do consider origins: - * - * - themes without theme.json: only the classes for the presets defined by core - * - themes with theme.json: the presets and styles classes, both from core and the theme - */ - $styles_rest = ''; - if ( ! empty( $types ) ) { - /* - * We only use the default, theme, and custom origins. - * This is because styles for blocks origin are added - * at a later phase (render cycle) so we only render the ones in use. - * @see wp_add_global_styles_for_blocks - */ - $origins = array( 'default', 'theme', 'custom' ); - if ( ! $supports_theme_json ) { - $origins = array( 'default' ); - } - $styles_rest = $tree->get_stylesheet( $types, $origins ); - } - $stylesheet = $styles_variables . $styles_rest; - if ( $can_use_cached ) { - // Cache for a minute. - // This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites. - set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS ); - } - return $stylesheet; -} diff --git a/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php index e10710e0f4709..7adb37ffbd9e0 100644 --- a/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php +++ b/lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php @@ -32,4 +32,29 @@ public static function theme_has_support() { return wp_theme_has_theme_json(); } + /** + * Private method to clean the cached data after an upgrade. + * + * It is hooked into the `upgrader_process_complete` action. + * + * @see default-filters.php + * + * @param WP_Upgrader $upgrader WP_Upgrader instance. + * @param array $options Array of bulk item update data. + */ + public static function _clean_cached_data_upon_upgrading( $upgrader, $options ) { + if ( 'update' !== $options['action'] ) { + return; + } + + if ( + 'core' === $options['type'] || + 'plugin' === $options['type'] || + // Clean cache only if the active theme was updated. + ( 'theme' === $options['type'] && ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) ) + ) { + static::clean_cached_data(); + } + } + } diff --git a/lib/compat/wordpress-6.2/default-filters.php b/lib/compat/wordpress-6.2/default-filters.php index 861b8a01421c6..927ff2bd12aa3 100644 --- a/lib/compat/wordpress-6.2/default-filters.php +++ b/lib/compat/wordpress-6.2/default-filters.php @@ -20,3 +20,13 @@ add_action( 'switch_theme', 'wp_theme_has_theme_json_clean_cache' ); add_action( 'start_previewing_theme', 'wp_theme_has_theme_json_clean_cache' ); add_action( 'upgrader_process_complete', '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme', 10, 2 ); +add_action( 'save_post_wp_global_styles', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) ); +add_action( 'activated_plugin', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) ); +add_action( 'deactivated_plugin', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) ); +add_action( 'upgrader_process_complete', array( 'WP_Theme_JSON_Resolver_Gutenberg', '_clean_cached_data_upon_upgrading', 10, 2 ) ); +add_action( 'save_post_wp_global_styles', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'switch_theme', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'start_previewing_theme', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'activated_plugin', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'deactivated_plugin', 'gutenberg_get_global_stylesheet_clean_cache' ); +add_action( 'upgrader_process_complete', '_gutenberg_get_global_stylesheet_clean_cache_upon_upgrading', 10, 2 ); diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index be9781556868c..5d37d3182ccc8 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -79,3 +79,113 @@ function _wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme( $upgr } } } + +/** + * Returns the stylesheet resulting of merging core, theme, and user data. + * + * @param array $types Types of styles to load. Optional. + * It accepts 'variables', 'styles', 'presets' as values. + * If empty, it'll load all for themes with theme.json support + * and only [ 'variables', 'presets' ] for themes without theme.json support. + * + * @return string Stylesheet. + */ +function gutenberg_get_global_stylesheet( $types = array() ) { + // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. + $can_use_cached = empty( $types ) && ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ); + $cache_key = 'gutenberg_get_global_stylesheet'; + $cache_group = 'theme_json'; + if ( $can_use_cached ) { + $cached = wp_cache_get( $cache_key, $cache_group ); + if ( $cached ) { + return $cached; + } + } + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $supports_theme_json = wp_theme_has_theme_json(); + if ( empty( $types ) && ! $supports_theme_json ) { + $types = array( 'variables', 'presets', 'base-layout-styles' ); + } elseif ( empty( $types ) ) { + $types = array( 'variables', 'styles', 'presets' ); + } + + /* + * If variables are part of the stylesheet, + * we add them. + * + * This is so themes without a theme.json still work as before 5.9: + * they can override the default presets. + * See https://core.trac.wordpress.org/ticket/54782 + */ + $styles_variables = ''; + if ( in_array( 'variables', $types, true ) ) { + /* + * We only use the default, theme, and custom origins. + * This is because styles for blocks origin are added + * at a later phase (render cycle) so we only render the ones in use. + * @see wp_add_global_styles_for_blocks + */ + $origins = array( 'default', 'theme', 'custom' ); + $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins ); + $types = array_diff( $types, array( 'variables' ) ); + } + + /* + * For the remaining types (presets, styles), we do consider origins: + * + * - themes without theme.json: only the classes for the presets defined by core + * - themes with theme.json: the presets and styles classes, both from core and the theme + */ + $styles_rest = ''; + if ( ! empty( $types ) ) { + /* + * We only use the default, theme, and custom origins. + * This is because styles for blocks origin are added + * at a later phase (render cycle) so we only render the ones in use. + * @see wp_add_global_styles_for_blocks + */ + $origins = array( 'default', 'theme', 'custom' ); + if ( ! $supports_theme_json ) { + $origins = array( 'default' ); + } + $styles_rest = $tree->get_stylesheet( $types, $origins ); + } + $stylesheet = $styles_variables . $styles_rest; + if ( $can_use_cached ) { + wp_cache_set( $cache_key, $stylesheet, $cache_group ); + } + return $stylesheet; +} + +/** + * Clean the cache used by the `gutenberg_get_global_stylesheet` function. + */ +function gutenberg_get_global_stylesheet_clean_cache() { + wp_cache_delete( 'gutenberg_get_global_stylesheet', 'theme_json' ); +} + +/** + * Private function to clean the cache used by the `gutenberg_get_global_stylesheet` function after an upgrade. + * + * It is hooked into the `upgrader_process_complete` action. + * + * @see default-filters.php + * + * @param WP_Upgrader $upgrader WP_Upgrader instance. + * @param array $options Array of bulk item update data. + */ +function _gutenberg_get_global_stylesheet_clean_cache_upon_upgrading( $upgrader, $options ) { + if ( 'update' !== $options['action'] ) { + return; + } + + if ( + 'core' === $options['type'] || + 'plugin' === $options['type'] || + // Clean cache only if the active theme was updated. + ( 'theme' === $options['type'] && ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) ) + ) { + gutenberg_get_global_stylesheet_clean_cache(); + } +} + diff --git a/phpunit/wp-get-global-stylesheet-test.php b/phpunit/wp-get-global-stylesheet-test.php new file mode 100644 index 0000000000000..cb4d0242ce3e3 --- /dev/null +++ b/phpunit/wp-get-global-stylesheet-test.php @@ -0,0 +1,98 @@ +user->create( + array( + 'role' => 'administrator', + 'user_email' => 'administrator@example.com', + ) + ); + } + + public function set_up() { + parent::set_up(); + + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + $this->theme_root = realpath( DIR_TESTDATA . '/themedir1' ); + + // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. + $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); + + // Set up the new root. + add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + + // Clear caches. + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + } + + public function tear_down() { + $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; + + // Clear up the filters to modify the theme root. + remove_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + remove_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + remove_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + + parent::tear_down(); + } + + public function filter_set_theme_root() { + return $this->theme_root; + } + + public function test_global_styles_user_cpt_change_invalidates_cached_stylesheet() { + add_filter( 'wp_get_global_stylesheet_can_use_cache', '__return_true' ); + switch_theme( 'block-theme' ); + wp_set_current_user( self::$administrator_id ); + + $styles = gutenberg_get_global_stylesheet(); + $this->assertStringNotContainsString( 'background-color: hotpink;', $styles ); + + $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( wp_get_theme(), true ); + $config = json_decode( $user_cpt['post_content'], true ); + $config['styles']['color']['background'] = 'hotpink'; + $user_cpt['post_content'] = wp_json_encode( $config ); + + wp_update_post( $user_cpt, true, false ); + + $styles = gutenberg_get_global_stylesheet(); + $this->assertStringContainsString( 'background-color: hotpink;', $styles ); + remove_filter( 'wp_get_global_stylesheet_can_use_cache', '__return_true' ); + } +} From 6088f5cfaaaee5ee9d378514af0e0e416b1a227e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:39:51 -0700 Subject: [PATCH 088/380] Update which origins are queried for `gutenberg_get_global_settings` (#45971) Co-authored-by: Felix Arntz --- .../get-global-styles-and-settings.php | 29 -------------- .../get-global-styles-and-settings.php | 38 +++++++++++++++++++ 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/lib/compat/wordpress-6.0/get-global-styles-and-settings.php b/lib/compat/wordpress-6.0/get-global-styles-and-settings.php index 9ed3e891182cb..d4f1f9ef03492 100644 --- a/lib/compat/wordpress-6.0/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.0/get-global-styles-and-settings.php @@ -5,35 +5,6 @@ * @package gutenberg */ -/** - * Function to get the settings resulting of merging core, theme, and user data. - * - * @param array $path Path to the specific setting to retrieve. Optional. - * If empty, will return all settings. - * @param array $context { - * Metadata to know where to retrieve the $path from. Optional. - * - * @type string $block_name Which block to retrieve the settings from. - * If empty, it'll return the settings for the global context. - * @type string $origin Which origin to take data from. - * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). - * If empty or unknown, 'all' is used. - * } - * - * @return array The settings to retrieve. - */ -function gutenberg_get_global_settings( $path = array(), $context = array() ) { - if ( ! empty( $context['block_name'] ) ) { - $path = array_merge( array( 'blocks', $context['block_name'] ), $path ); - } - $origin = 'custom'; - if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) { - $origin = 'theme'; - } - $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings(); - return _wp_array_get( $settings, $path, $settings ); -} - /** * Function to get the styles resulting of merging core, theme, and user data. * diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index 5d37d3182ccc8..cee145acca5fc 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -189,3 +189,41 @@ function _gutenberg_get_global_stylesheet_clean_cache_upon_upgrading( $upgrader, } } +/** + * Function to get the settings resulting of merging core, theme, and user data. + * + * @param array $path Path to the specific setting to retrieve. Optional. + * If empty, will return all settings. + * @param array $context { + * Metadata to know where to retrieve the $path from. Optional. + * + * @type string $block_name Which block to retrieve the settings from. + * If empty, it'll return the settings for the global context. + * @type string $origin Which origin to take data from. + * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). + * If empty or unknown, 'all' is used. + * } + * + * @return array The settings to retrieve. + */ +function gutenberg_get_global_settings( $path = array(), $context = array() ) { + if ( ! empty( $context['block_name'] ) ) { + $new_path = array( 'blocks', $context['block_name'] ); + foreach ( $path as $subpath ) { + $new_path[] = $subpath; + } + $path = $new_path; + } + + // This is the default value when no origin is provided or when it is 'all'. + $origin = 'custom'; + if ( + ! wp_theme_has_theme_json() || + ( isset( $context['origin'] ) && 'base' === $context['origin'] ) + ) { + $origin = 'theme'; + } + + $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_settings(); + return _wp_array_get( $settings, $path, $settings ); +} From 4183090b2030efce7f946f88cc3bdbb22ed6f9d5 Mon Sep 17 00:00:00 2001 From: Jonny Harris Date: Tue, 22 Nov 2022 20:30:38 +0000 Subject: [PATCH 089/380] Cleaner logic. (#45950) --- .../get-global-styles-and-settings.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php index cee145acca5fc..e74ef3144ef09 100644 --- a/lib/compat/wordpress-6.2/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.2/get-global-styles-and-settings.php @@ -25,23 +25,23 @@ function wp_theme_has_theme_json() { * The reason not to store it as a boolean is to avoid working * with the $found parameter which apparently had some issues in some implementations * https://developer.wordpress.org/reference/functions/wp_cache_get/ + * + * Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. */ - if ( - // Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow. - ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) && - ( 0 === $theme_has_support || 1 === $theme_has_support ) - ) { + if ( ! WP_DEBUG && is_int( $theme_has_support ) ) { return (bool) $theme_has_support; } // Has the own theme a theme.json? - $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ) ? 1 : 0; + $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ); // Look up the parent if the child does not have a theme.json. - if ( 0 === $theme_has_support ) { - $theme_has_support = is_readable( get_template_directory() . '/theme.json' ) ? 1 : 0; + if ( ! $theme_has_support ) { + $theme_has_support = is_readable( get_template_directory() . '/theme.json' ); } + $theme_has_support = $theme_has_support ? 1 : 0; + wp_cache_set( $cache_key, $theme_has_support, $cache_group ); return (bool) $theme_has_support; From 7e76592a8fec0ee9e6a8dcb3cfda558fbc07ecec Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Tue, 15 Nov 2022 16:36:30 -0700 Subject: [PATCH 090/380] Tag Processor: Add ability to stop at tag closers, if requested `WP_HTML_Tag_Processor` introduces the ability to walk through an HTML document and modify attributes on tag openers. At times it could be useful to stop also at tag closers in order to perform augmented inspection and querying of a document. In this patch we're opening up a mode that allows just that; if querying with the `[ "tag_closers" => "visit" ]` query parrameter then in addition to stopping at tag openers, this will allow stopping at all tag tokens regardless of open or close status. --- .../html/class-wp-html-tag-processor.php | 137 +++++++++++++----- phpunit/html/wp-html-tag-processor-test.php | 56 +++++++ 2 files changed, 159 insertions(+), 34 deletions(-) diff --git a/lib/experimental/html/class-wp-html-tag-processor.php b/lib/experimental/html/class-wp-html-tag-processor.php index 6e72fc38ff411..acdf8d97a95a9 100644 --- a/lib/experimental/html/class-wp-html-tag-processor.php +++ b/lib/experimental/html/class-wp-html-tag-processor.php @@ -221,6 +221,14 @@ class WP_HTML_Tag_Processor { */ private $sought_match_offset; + /** + * Whether to visit tag closers, e.g.
    , when walking an input document. + * + * @since 6.2.0 + * @var boolean + */ + private $stop_on_tag_closers; + /** * The updated HTML document. * @@ -276,6 +284,29 @@ class WP_HTML_Tag_Processor { */ private $tag_name_length; + /** + * Byte offset in input document where current tag token ends. + * + * Example: + * ``` + *
    ... + * 0 1 | + * 01234567890123456 + * --- tag name ends at 14 + * ``` + * + * @since 6.2.0 + * @var ?int + */ + private $tag_ends_at; + + /** + * Whether the current tag is an opening tag, e.g.
    , or a closing tag, e.g.
    . + * + * @var boolean + */ + private $is_closing_tag; + /** * Lazily-built index of attributes found within an HTML tag, keyed by the attribute name. * @@ -412,7 +443,16 @@ public function next_tag( $query = null ) { return false; } - $this->parse_tag_opener_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } + + $tag_ends_at = strpos( $this->html, '>', $this->parsed_bytes ); + if ( false === $tag_ends_at ) { + return false; + } + $this->tag_ends_at = $tag_ends_at; + $this->parsed_bytes = $tag_ends_at; if ( $this->matches() ) { ++$already_found; @@ -495,7 +535,9 @@ private function skip_rcdata( $tag_name ) { continue; } - $this->skip_tag_closer_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } $at = $this->parsed_bytes; if ( $at >= strlen( $this->html ) ) { return false; @@ -620,12 +662,14 @@ private function skip_script_data() { if ( $is_closing ) { $this->parsed_bytes = $at; - $this->skip_tag_closer_attributes(); - if ( $this->parsed_bytes >= $doc_length ) { return false; } + while ( $this->parse_next_attribute() ) { + continue; + } + if ( '>' === $html[ $this->parsed_bytes ] ) { ++$this->parsed_bytes; return true; @@ -656,6 +700,13 @@ private function parse_next_tag() { return false; } + if ( '/' === $this->html[ $at + 1 ] ) { + $this->is_closing_tag = true; + $at++; + } else { + $this->is_closing_tag = false; + } + /* * HTML tag names must start with [a-zA-Z] otherwise they are not tags. * For example, "<3" is rendered as text, not a tag opener. This means @@ -777,35 +828,12 @@ private function parse_next_tag() { return false; } - /** - * Parses all attributes of the current tag. - * - * @since 6.2.0 - */ - private function parse_tag_opener_attributes() { - while ( $this->parse_next_attribute() ) { - continue; - } - } - - /** - * Skips all attributes of the current tag. - * - * @since 6.2.0 - */ - private function skip_tag_closer_attributes() { - while ( $this->parse_next_attribute( 'tag-closer' ) ) { - continue; - } - } - /** * Parses the next attribute. * - * @param string $context tag-opener or tag-closer. * @since 6.2.0 */ - private function parse_next_attribute( $context = 'tag-opener' ) { + private function parse_next_attribute() { // Skip whitespace and slashes. $this->parsed_bytes += strspn( $this->html, " \t\f\r\n/", $this->parsed_bytes ); if ( $this->parsed_bytes >= strlen( $this->html ) ) { @@ -872,7 +900,7 @@ private function parse_next_attribute( $context = 'tag-opener' ) { return false; } - if ( 'tag-opener' !== $context ) { + if ( $this->is_closing_tag ) { return true; } @@ -914,6 +942,8 @@ private function after_tag() { $this->apply_attributes_updates(); $this->tag_name_starts_at = null; $this->tag_name_length = null; + $this->tag_ends_at = null; + $this->is_closing_tag = null; $this->attributes = array(); } @@ -1159,6 +1189,25 @@ public function get_tag() { return strtoupper( $tag_name ); } + /** + * Indicates if the current tag token is a tag closer. + * + * Example: + * + * $p = new WP_HTML_Tag_Processor( '
    ' ); + * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] ); + * $p->is_tag_closer() === false; + * + * $p->next_tag( [ 'tag_name' => 'div', 'tag_closers' => 'visit' ] ); + * $p->is_tag_closer() === true; + *
    + * + * @return bool + */ + public function is_tag_closer() { + return $this->is_closing_tag; + } + /** * Updates or creates a new attribute on the currently matched tag with the value passed. * @@ -1175,8 +1224,8 @@ public function get_tag() { * @throws Exception When WP_DEBUG is true and the attribute name is invalid. */ public function set_attribute( $name, $value ) { - if ( null === $this->tag_name_starts_at ) { - return; + if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) { + return false; } /* @@ -1286,8 +1335,8 @@ public function set_attribute( $name, $value ) { * @param string $name The attribute name to remove. */ public function remove_attribute( $name ) { - if ( ! isset( $this->attributes[ $name ] ) ) { - return; + if ( $this->is_closing_tag || ! isset( $this->attributes[ $name ] ) ) { + return false; } /* @@ -1316,6 +1365,10 @@ public function remove_attribute( $name ) { * @param string $class_name The class name to add. */ public function add_class( $class_name ) { + if ( $this->is_closing_tag ) { + return false; + } + if ( null !== $this->tag_name_starts_at ) { $this->classname_updates[ $class_name ] = self::ADD_CLASS; } @@ -1329,6 +1382,10 @@ public function add_class( $class_name ) { * @param string $class_name The class name to remove. */ public function remove_class( $class_name ) { + if ( $this->is_closing_tag ) { + return false; + } + if ( null !== $this->tag_name_starts_at ) { $this->classname_updates[ $class_name ] = self::REMOVE_CLASS; } @@ -1392,7 +1449,9 @@ public function get_updated_html() { // Parse the attributes in the updated markup. $this->attributes = array(); - $this->parse_tag_opener_attributes(); + while ( $this->parse_next_attribute() ) { + continue; + } return $this->html; } @@ -1407,6 +1466,7 @@ public function get_updated_html() { * * @type string|null $tag_name Which tag to find, or `null` for "any tag." * @type string|null $class_name Tag must contain this class name to match. + * @type string $tag_closers "visit" or "skip": whether to stop on tag closers, e.g.
    . * } */ private function parse_query( $query ) { @@ -1418,6 +1478,7 @@ private function parse_query( $query ) { $this->sought_tag_name = null; $this->sought_class_name = null; $this->sought_match_offset = 1; + $this->stop_on_tag_closers = false; // A single string value means "find the tag of this name". if ( is_string( $query ) ) { @@ -1441,6 +1502,10 @@ private function parse_query( $query ) { if ( isset( $query['match_offset'] ) && is_int( $query['match_offset'] ) && 0 < $query['match_offset'] ) { $this->sought_match_offset = $query['match_offset']; } + + if ( isset( $query['tag_closers'] ) ) { + $this->stop_on_tag_closers = 'visit' === $query['tag_closers']; + } } @@ -1452,6 +1517,10 @@ private function parse_query( $query ) { * @return boolean */ private function matches() { + if ( $this->is_closing_tag && ! $this->stop_on_tag_closers ) { + return false; + } + // Do we match a case-insensitive HTML tag name? if ( null !== $this->sought_tag_name ) { /* diff --git a/phpunit/html/wp-html-tag-processor-test.php b/phpunit/html/wp-html-tag-processor-test.php index e66b1d50758d0..273cbddddea4b 100644 --- a/phpunit/html/wp-html-tag-processor-test.php +++ b/phpunit/html/wp-html-tag-processor-test.php @@ -237,6 +237,35 @@ public function test_next_tag_should_return_false_for_a_non_existing_tag() { $this->assertFalse( $p->next_tag( 'p' ), 'Querying a non-existing tag did not return false' ); } + /** + * @covers next_tag + * @covers is_tag_closer + */ + public function test_next_tag_should_stop_on_closers_only_when_requested() { + $p = new WP_HTML_Tag_Processor( '
    ' ); + $this->assertTrue( $p->next_tag( array( 'tag_name' => 'div' ) ), 'Did not find desired tag opener' ); + $this->assertFalse( $p->next_tag( array( 'tag_name' => 'div' ) ), 'Visited an unwanted tag, a tag closer' ); + + $p = new WP_HTML_Tag_Processor( '
    ' ); + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ); + $this->assertFalse( $p->is_tag_closer(), 'Indicated a tag opener is a tag closer' ); + $this->assertTrue( + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ), + 'Did not stop at desired tag closer' + ); + $this->assertTrue( $p->is_tag_closer(), 'Indicated a tag closer is a tag opener' ); + } + /** * @ticket 56299 * @@ -255,6 +284,33 @@ public function test_set_attribute_on_a_non_existing_tag_does_not_change_the_mar ); } + public function test_attribute_ops_on_tag_closer_do_not_change_the_markup() { + $p = new WP_HTML_Tag_Processor( '
    ' ); + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ); + $this->assertFalse( $p->is_tag_closer(), 'Skipped tag opener' ); + $p->next_tag( + array( + 'tag_name' => 'div', + 'tag_closers' => 'visit', + ) + ); + $this->assertTrue( $p->is_tag_closer(), 'Skipped tag closer' ); + $this->assertFalse( $p->set_attribute( 'id', 'test' ), "Allowed setting an attribute on a tag closer when it shouldn't have" ); + $this->assertFalse( $p->remove_attribute( 'invalid-id' ), "Allowed removing an attribute on a tag closer when it shouldn't have" ); + $this->assertFalse( $p->add_class( 'sneaky' ), "Allowed adding a class on a tag closer when it shouldn't have" ); + $this->assertFalse( $p->remove_class( 'not-appearing-in-this-test' ), "Allowed removing a class on a tag closer when it shouldn't have" ); + $this->assertSame( + '
    ', + $p->get_updated_html(), + 'Calling get_updated_html after updating a non-existing tag returned an HTML that was different from the original HTML' + ); + } + /** * Passing a double quote inside of an attribute values could lead to an XSS attack as follows: * From d98e24e94bd6208be85196d409c8e63c8db5627b Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 23 Nov 2022 18:59:44 +1300 Subject: [PATCH 091/380] Colorize template parts and Reusable blocks (#45473) --- packages/base-styles/_colors.scss | 7 ++++++ .../src/components/block-card/index.js | 12 +++++++++- .../src/components/block-card/style.scss | 4 ++++ .../block-content-overlay/style.scss | 8 ++++++- .../src/components/block-toolbar/index.js | 17 +++++++++---- .../src/components/block-toolbar/style.scss | 10 ++++++++ .../components/inserter-list-item/index.js | 12 +++++++++- .../components/inserter-list-item/style.scss | 5 ++++ .../src/components/list-view/block.js | 3 +++ .../src/components/list-view/branch.js | 7 ++++++ .../src/components/list-view/style.scss | 24 +++++++++++++++---- .../use-block-display-information/index.js | 19 +++++++++++---- packages/block-library/src/block/editor.scss | 17 +++++++++++++ .../src/template-part/editor.scss | 22 +++++++++++++++-- packages/blocks/src/api/registration.js | 2 +- .../document-actions/index.js | 23 +++++++++++------- .../document-actions/style.scss | 8 ++++++- .../components/template-details/style.scss | 4 ++++ 18 files changed, 173 insertions(+), 31 deletions(-) diff --git a/packages/base-styles/_colors.scss b/packages/base-styles/_colors.scss index 03f6bfbdd566d..5847a66f8b243 100644 --- a/packages/base-styles/_colors.scss +++ b/packages/base-styles/_colors.scss @@ -1,3 +1,5 @@ +@import "./functions"; + /** * Colors */ @@ -24,3 +26,8 @@ $light-gray-placeholder: rgba($white, 0.65); $alert-yellow: #f0b849; $alert-red: #cc1818; $alert-green: #4ab866; + +:root { + --wp-block-synced-color: #7a00df; + --wp-block-synced-color--rgb: #{hex-to-rgb(#7a00df)}; +} diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index 6362b1b4ca2c7..afda93d5ece7a 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -17,6 +22,7 @@ function BlockCard( { blockType, parentBlockClientId, handleBackButton, + isSynced, } ) { if ( blockType ) { deprecated( '`blockType` property in `BlockCard component`', { @@ -30,7 +36,11 @@ function BlockCard( { window?.__experimentalEnableOffCanvasNavigationEditor === true; return ( -
    +
    { isOffCanvasNavigationEditorEnabled && parentBlockClientId && ( ) } { ! canOnlyChangeValues && ( @@ -454,19 +482,40 @@ export default function PaletteEdit( { isGradient={ isGradient } /> ) } + { ! isEditing && editingElement !== null && ( + setEditingElement( null ) } + onChange={ ( newElement ) => { + debounceOnChange( + elements.map( + ( currentElement, currentIndex ) => { + if ( + currentIndex === editingElement + ) { + return newElement; + } + return currentElement; + } + ) + ); + } } + element={ elements[ editingElement ] } + /> + ) } { ! isEditing && ( isGradient ? ( {} } + onChange={ onSelectPaletteItem } clearable={ false } disableCustomGradients={ true } /> ) : ( {} } + onChange={ onSelectPaletteItem } clearable={ false } disableCustomColors={ true } /> From 2982e4f8c8af9f4e7cceba100c1fb2606262dc3a Mon Sep 17 00:00:00 2001 From: brookewp <35543432+brookewp@users.noreply.github.com> Date: Wed, 23 Nov 2022 15:33:18 -0800 Subject: [PATCH 105/380] ComboboxControl: replace margin overides with new opt-in prop (#45796) --- packages/block-library/src/avatar/user-control.js | 1 + packages/block-library/src/post-author/edit.js | 1 + packages/editor/src/components/page-attributes/parent.js | 1 + packages/editor/src/components/post-author/combobox.js | 1 + 4 files changed, 4 insertions(+) diff --git a/packages/block-library/src/avatar/user-control.js b/packages/block-library/src/avatar/user-control.js index 49370b39716ef..598c05011eaed 100644 --- a/packages/block-library/src/avatar/user-control.js +++ b/packages/block-library/src/avatar/user-control.js @@ -33,6 +33,7 @@ function UserControl( { value, onChange } ) { return ( Date: Wed, 23 Nov 2022 15:33:51 -0800 Subject: [PATCH 106/380] FocalPointPicker: add new opt-in prop (#45958) --- packages/block-library/src/cover/edit/inspector-controls.js | 1 + packages/block-library/src/media-text/edit.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/block-library/src/cover/edit/inspector-controls.js b/packages/block-library/src/cover/edit/inspector-controls.js index cfcf012badd62..98747988c8f40 100644 --- a/packages/block-library/src/cover/edit/inspector-controls.js +++ b/packages/block-library/src/cover/edit/inspector-controls.js @@ -162,6 +162,7 @@ export default function CoverInspectorControls( { ) } { showFocalPointPicker && ( Date: Thu, 24 Nov 2022 12:14:00 +1100 Subject: [PATCH 107/380] Min Height: Add height control component with slider (#45875) * Min Height: Add height control component with slider * Fix typo for rem unit Co-authored-by: Ramon * Add some handling for switching between unit types * Add storybook example Co-authored-by: Ramon --- .../src/components/height-control/index.js | 123 ++++++++++++++++++ .../height-control/stories/index.js | 21 +++ .../src/components/height-control/style.scss | 5 + packages/block-editor/src/components/index.js | 1 + packages/block-editor/src/hooks/dimensions.js | 1 - packages/block-editor/src/hooks/min-height.js | 21 +-- packages/block-editor/src/style.scss | 1 + .../global-styles/dimensions-panel.js | 7 +- 8 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 packages/block-editor/src/components/height-control/index.js create mode 100644 packages/block-editor/src/components/height-control/stories/index.js create mode 100644 packages/block-editor/src/components/height-control/style.scss diff --git a/packages/block-editor/src/components/height-control/index.js b/packages/block-editor/src/components/height-control/index.js new file mode 100644 index 0000000000000..cc1eea0b4bad8 --- /dev/null +++ b/packages/block-editor/src/components/height-control/index.js @@ -0,0 +1,123 @@ +/** + * WordPress dependencies + */ +import { useMemo } from '@wordpress/element'; +import { + BaseControl, + RangeControl, + Flex, + FlexItem, + __experimentalSpacer as Spacer, + __experimentalUseCustomUnits as useCustomUnits, + __experimentalUnitControl as UnitControl, + __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import useSetting from '../use-setting'; + +const RANGE_CONTROL_CUSTOM_SETTINGS = { + px: { max: 1000, step: 1 }, + '%': { max: 100, step: 1 }, + vw: { max: 100, step: 1 }, + vh: { max: 100, step: 1 }, + em: { max: 50, step: 0.1 }, + rem: { max: 50, step: 0.1 }, +}; + +export default function HeightControl( { + onChange, + label = __( 'Height' ), + value, +} ) { + const customRangeValue = parseFloat( value ); + + const units = useCustomUnits( { + availableUnits: useSetting( 'spacing.units' ) || [ + '%', + 'px', + 'em', + 'rem', + 'vh', + 'vw', + ], + } ); + + const selectedUnit = + useMemo( + () => parseQuantityAndUnitFromRawValue( value ), + [ value ] + )[ 1 ] || + units[ 0 ]?.value || + 'px'; + + const handleSliderChange = ( next ) => { + onChange( [ next, selectedUnit ].join( '' ) ); + }; + + const handleUnitChange = ( newUnit ) => { + // Attempt to smooth over differences between currentUnit and newUnit. + // This should slightly improve the experience of switching between unit types. + const [ currentValue, currentUnit ] = + parseQuantityAndUnitFromRawValue( value ); + + if ( [ 'em', 'rem' ].includes( newUnit ) && currentUnit === 'px' ) { + // Convert pixel value to an approximate of the new unit, assuming a root size of 16px. + onChange( ( currentValue / 16 ).toFixed( 2 ) + newUnit ); + } else if ( + [ 'em', 'rem' ].includes( currentUnit ) && + newUnit === 'px' + ) { + // Convert to pixel value assuming a root size of 16px. + onChange( Math.round( currentValue * 16 ) + newUnit ); + } else if ( + [ 'vh', 'vw', '%' ].includes( newUnit ) && + currentValue > 100 + ) { + // When converting to `vh`, `vw`, or `%` units, cap the new value at 100. + onChange( 100 + newUnit ); + } + }; + + return ( +
    + + { label } + + + + + + + + + + + +
    + ); +} diff --git a/packages/block-editor/src/components/height-control/stories/index.js b/packages/block-editor/src/components/height-control/stories/index.js new file mode 100644 index 0000000000000..f4b586a96b0e3 --- /dev/null +++ b/packages/block-editor/src/components/height-control/stories/index.js @@ -0,0 +1,21 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import HeightControl from '../'; + +export default { + component: HeightControl, + title: 'BlockEditor/HeightControl', +}; + +const Template = ( props ) => { + const [ value, setValue ] = useState(); + return ; +}; + +export const Default = Template.bind( {} ); diff --git a/packages/block-editor/src/components/height-control/style.scss b/packages/block-editor/src/components/height-control/style.scss new file mode 100644 index 0000000000000..add0866835f76 --- /dev/null +++ b/packages/block-editor/src/components/height-control/style.scss @@ -0,0 +1,5 @@ +.block-editor-height-control { + border: 0; + margin: 0; + padding: 0; +} diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 7743a98066c43..03ce8f3880ad3 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -53,6 +53,7 @@ export { default as __experimentalColorGradientControl } from './colors-gradient export { default as __experimentalColorGradientSettingsDropdown } from './colors-gradients/dropdown'; export { default as __experimentalPanelColorGradientSettings } from './colors-gradients/panel-color-gradient-settings'; export { default as __experimentalUseMultipleOriginColorsAndGradients } from './colors-gradients/use-multiple-origin-colors-and-gradients'; +export { default as __experimentalHeightControl } from './height-control'; export { default as __experimentalImageEditor, ImageEditingProvider as __experimentalImageEditingProvider, diff --git a/packages/block-editor/src/hooks/dimensions.js b/packages/block-editor/src/hooks/dimensions.js index 44a9fd83278a9..3b33df400fcfc 100644 --- a/packages/block-editor/src/hooks/dimensions.js +++ b/packages/block-editor/src/hooks/dimensions.js @@ -182,7 +182,6 @@ export function DimensionsPanel( props ) { ) } { ! isMinHeightDisabled && ( hasMinHeightValue( props ) } label={ __( 'Min. height' ) } onDeselect={ () => resetMinHeight( props ) } diff --git a/packages/block-editor/src/hooks/min-height.js b/packages/block-editor/src/hooks/min-height.js index 3167edba8a829..e123f0cee98b2 100644 --- a/packages/block-editor/src/hooks/min-height.js +++ b/packages/block-editor/src/hooks/min-height.js @@ -2,16 +2,13 @@ * WordPress dependencies */ import { getBlockSupport } from '@wordpress/blocks'; -import { - __experimentalUseCustomUnits as useCustomUnits, - __experimentalUnitControl as UnitControl, -} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import useSetting from '../components/use-setting'; +import HeightControl from '../components/height-control'; import { DIMENSIONS_SUPPORT_KEY } from './dimensions'; import { cleanEmptyObject } from './utils'; @@ -81,17 +78,6 @@ export function MinHeightEdit( props ) { setAttributes, } = props; - const units = useCustomUnits( { - availableUnits: useSetting( 'dimensions.units' ) || [ - '%', - 'px', - 'em', - 'rem', - 'vh', - 'vw', - ], - } ); - if ( useIsMinHeightDisabled( props ) ) { return null; } @@ -109,13 +95,10 @@ export function MinHeightEdit( props ) { }; return ( - ); } diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 0073742361158..6db197a389d21 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -32,6 +32,7 @@ @import "./components/date-format-picker/style.scss"; @import "./components/duotone-control/style.scss"; @import "./components/font-appearance-control/style.scss"; +@import "./components/height-control/style.scss"; @import "./components/image-size-control/style.scss"; @import "./components/inner-blocks/style.scss"; @import "./components/inserter-list-item/style.scss"; diff --git a/packages/edit-site/src/components/global-styles/dimensions-panel.js b/packages/edit-site/src/components/global-styles/dimensions-panel.js index d27a109e2b752..1b88bc4074dc8 100644 --- a/packages/edit-site/src/components/global-styles/dimensions-panel.js +++ b/packages/edit-site/src/components/global-styles/dimensions-panel.js @@ -18,6 +18,7 @@ import { } from '@wordpress/components'; import { __experimentalUseCustomSides as useCustomSides, + __experimentalHeightControl as HeightControl, __experimentalSpacingSizesControl as SpacingSizesControl, } from '@wordpress/block-editor'; import { Icon, positionCenter, stretchWide } from '@wordpress/icons'; @@ -556,19 +557,15 @@ export default function DimensionsPanel( { name } ) { ) } { showMinHeightControl && ( - ) } From 08ae94ce2408bb486f1d6c669e35cfe6c2691c4b Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 24 Nov 2022 15:36:27 +1300 Subject: [PATCH 108/380] Pass the is-synced classname into the BlockCard instead of the isSynced prop (#46021) --- packages/block-editor/src/components/block-card/index.js | 8 ++------ .../block-editor/src/components/block-inspector/index.js | 6 +++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index afda93d5ece7a..c85fd3ff21dd3 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -22,7 +22,7 @@ function BlockCard( { blockType, parentBlockClientId, handleBackButton, - isSynced, + className, } ) { if ( blockType ) { deprecated( '`blockType` property in `BlockCard component`', { @@ -36,11 +36,7 @@ function BlockCard( { window?.__experimentalEnableOffCanvasNavigationEditor === true; return ( -
    +
    { isOffCanvasNavigationEditorEnabled && parentBlockClientId && ( - ); - } ) } -
    -
    - ); -} - export default function NavigationLinkEdit( { attributes, isSelected, @@ -460,7 +257,7 @@ export default function NavigationLinkEdit( { opensInNewTab, title: label && navStripHTML( label ), // don't allow HTML to display inside the }; - const { saveEntityRecord } = useDispatch( coreStore ); + const { replaceBlock, __unstableMarkNextChangeAsNotPersistent } = useDispatch( blockEditorStore ); const [ isLinkOpen, setIsLinkOpen ] = useState( false ); @@ -618,33 +415,6 @@ export default function NavigationLinkEdit( { userCanCreate = postsPermissions.canCreate; } - async function handleCreate( pageTitle ) { - const postType = type || 'page'; - - const page = await saveEntityRecord( 'postType', postType, { - title: pageTitle, - status: 'draft', - } ); - - return { - id: page.id, - type: postType, - // Make `title` property consistent with that in `fetchLinkSuggestions` where the `rendered` title (containing HTML entities) - // is also being decoded. By being consistent in both locations we avoid having to branch in the rendering output code. - // Ideally in the future we will update both APIs to utilise the "raw" form of the title which is better suited to edit contexts. - // e.g. - // - title.raw = "Yes & No" - // - title.rendered = "Yes & No" - // - decodeEntities( title.rendered ) = "Yes & No" - // See: - // - https://github.com/WordPress/gutenberg/pull/41063 - // - https://github.com/WordPress/gutenberg/blob/a1e1fdc0e6278457e9f4fc0b31ac6d2095f5450b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js#L212-L218 - title: decodeEntities( page.title.rendered ), - url: page.link, - kind: 'post-type', - }; - } - const { textColor, customTextColor, @@ -867,7 +637,7 @@ export default function NavigationLinkEdit( { // Ideally they would be stored in a raw, unescaped form. // Unescape is used here to "recover" the escaped characters // so they display without encoding. - // See `updateNavigationLinkBlockAttributes` for more details. + // See `updateAttributes` for more details. `${ unescape( label ) } ${ placeholderText }`.trim() @@ -883,64 +653,22 @@ export default function NavigationLinkEdit( { ) } { isLinkOpen && ( - setIsLinkOpen( false ) } anchor={ popoverAnchor } - shift - > - { - let format; - if ( type === 'post' ) { - /* translators: %s: search term. */ - format = __( - 'Create draft post: %s' - ); - } else { - /* translators: %s: search term. */ - format = __( - 'Create draft page: %s' - ); - } - return createInterpolateElement( - sprintf( format, searchTerm ), - { mark: } - ); - } } - noDirectEntry={ !! type } - noURLSuggestion={ !! type } - suggestionsQuery={ getSuggestionsQuery( - type, - kind - ) } - onChange={ ( updatedValue ) => - updateNavigationLinkBlockAttributes( - updatedValue, - setAttributes, - attributes - ) - } - onRemove={ removeLink } - renderControlBottom={ - ! url - ? () => ( - - ) - : null - } - /> - + hasCreateSuggestion={ userCanCreate } + onRemove={ removeLink } + onChange={ ( updatedValue ) => { + updateAttributes( + updatedValue, + setAttributes, + attributes + ); + } } + /> ) }
    diff --git a/packages/block-library/src/navigation-link/link-ui.js b/packages/block-library/src/navigation-link/link-ui.js new file mode 100644 index 0000000000000..5dfc3cffc1f31 --- /dev/null +++ b/packages/block-library/src/navigation-link/link-ui.js @@ -0,0 +1,202 @@ +/** + * WordPress dependencies + */ +import { Popover, Button } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { + __experimentalLinkControl as LinkControl, + BlockIcon, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { createInterpolateElement } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; +import { decodeEntities } from '@wordpress/html-entities'; +import { switchToBlockType } from '@wordpress/blocks'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * 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/site-logo', + 'core/social-links', + 'core/search', + ]; + const transforms = blockTransforms.filter( ( item ) => { + return featuredBlocks.includes( item.name ); + } ); + + if ( ! transforms?.length ) { + return null; + } + + return ( +
    +

    + { __( 'Transform' ) } +

    +
    + { transforms.map( ( item, index ) => { + return ( + + ); + } ) } +
    +
    + ); +} + +export function LinkUI( props ) { + const { saveEntityRecord } = useDispatch( coreStore ); + + async function handleCreate( pageTitle ) { + const postType = props.linkAttributes.type || 'page'; + + const page = await saveEntityRecord( 'postType', postType, { + title: pageTitle, + status: 'draft', + } ); + + return { + id: page.id, + type: postType, + // Make `title` property consistent with that in `fetchLinkSuggestions` where the `rendered` title (containing HTML entities) + // is also being decoded. By being consistent in both locations we avoid having to branch in the rendering output code. + // Ideally in the future we will update both APIs to utilise the "raw" form of the title which is better suited to edit contexts. + // e.g. + // - title.raw = "Yes & No" + // - title.rendered = "Yes & No" + // - decodeEntities( title.rendered ) = "Yes & No" + // See: + // - https://github.com/WordPress/gutenberg/pull/41063 + // - https://github.com/WordPress/gutenberg/blob/a1e1fdc0e6278457e9f4fc0b31ac6d2095f5450b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js#L212-L218 + title: decodeEntities( page.title.rendered ), + url: page.link, + kind: 'post-type', + }; + } + + return ( + + { + let format; + + if ( props.linkAttributes.type === 'post' ) { + /* translators: %s: search term. */ + format = __( 'Create draft post: %s' ); + } else { + /* translators: %s: search term. */ + format = __( 'Create draft page: %s' ); + } + + return createInterpolateElement( + sprintf( format, searchTerm ), + { + mark: , + } + ); + } } + noDirectEntry={ !! props.linkAttributes.type } + noURLSuggestion={ !! props.linkAttributes.type } + suggestionsQuery={ getSuggestionsQuery( + props.linkAttributes.type, + props.linkAttributes.kind + ) } + onChange={ props.onChange } + onRemove={ props.onRemove } + renderControlBottom={ + ! props.linkAttributes.url + ? () => ( + + ) + : null + } + /> + + ); +} diff --git a/packages/block-library/src/navigation-link/test/edit.js b/packages/block-library/src/navigation-link/test/edit.js index 8d052a5e3f133..ec396a64b2058 100644 --- a/packages/block-library/src/navigation-link/test/edit.js +++ b/packages/block-library/src/navigation-link/test/edit.js @@ -1,10 +1,10 @@ /** * Internal dependencies */ -import { updateNavigationLinkBlockAttributes } from '../edit'; +import { updateAttributes } from '../update-attributes'; describe( 'edit', () => { - describe( 'updateNavigationLinkBlockAttributes', () => { + describe( 'updateAttributes', () => { // Data shapes are linked to fetchLinkSuggestions from // core-data/src/fetch/__experimental-fetch-link-suggestions.js. it( 'can update a post link', () => { @@ -18,10 +18,7 @@ describe( 'edit', () => { type: 'post', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 1337, label: 'Menu Test', @@ -42,10 +39,7 @@ describe( 'edit', () => { type: 'page', url: 'http://wordpress.local/sample-page/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 2, kind: 'post-type', @@ -66,10 +60,7 @@ describe( 'edit', () => { type: 'post_tag', url: 'http://wordpress.local/tag/bar/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 15, kind: 'taxonomy', @@ -90,10 +81,7 @@ describe( 'edit', () => { type: 'category', url: 'http://wordpress.local/category/cats/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 9, kind: 'taxonomy', @@ -114,10 +102,7 @@ describe( 'edit', () => { type: 'portfolio', url: 'http://wordpress.local/portfolio/fall/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 131, kind: 'post-type', @@ -138,10 +123,7 @@ describe( 'edit', () => { type: 'portfolio_tag', url: 'http://wordpress.local/portfolio_tag/PortfolioTag/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 4, kind: 'taxonomy', @@ -162,10 +144,7 @@ describe( 'edit', () => { type: 'portfolio_category', url: 'http://wordpress.local/portfolio_category/Portfolio-category/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { id: 2, kind: 'taxonomy', @@ -186,10 +165,7 @@ describe( 'edit', () => { type: 'post-format', url: 'http://wordpress.local/type/video/', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); // post_format returns a slug ID value from the Search API // we do not persist this ID since we expect this value to be a post or term ID. expect( setAttributes ).toHaveBeenCalledWith( { @@ -208,10 +184,7 @@ describe( 'edit', () => { opensInNewTab: false, url: 'www.wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, url: 'www.wordpress.org', @@ -229,10 +202,7 @@ describe( 'edit', () => { type: 'URL', url: 'http://www.wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'www.wordpress.org', @@ -250,10 +220,7 @@ describe( 'edit', () => { type: 'mailto', url: 'mailto:foo@example.com', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'mailto:foo@example.com', @@ -272,10 +239,7 @@ describe( 'edit', () => { type: 'internal', url: '#foo', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: '#foo', @@ -294,10 +258,7 @@ describe( 'edit', () => { type: 'tel', url: 'tel:5555555', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'tel:5555555', @@ -319,10 +280,7 @@ describe( 'edit', () => { type: 'URL', url: 'https://www.wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'www.wordpress.org', @@ -339,10 +297,7 @@ describe( 'edit', () => { type: 'URL', url: 'wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'Custom Title', @@ -359,10 +314,7 @@ describe( 'edit', () => { type: 'URL', url: 'https://wordpress.org', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'Custom Title', @@ -380,10 +332,7 @@ describe( 'edit', () => { type: 'URL', url: 'http://wordpress.org/?s=<>', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes - ); + updateAttributes( linkSuggestion, setAttributes ); expect( setAttributes ).toHaveBeenCalledWith( { opensInNewTab: false, label: 'Custom Title', @@ -408,11 +357,7 @@ describe( 'edit', () => { type: 'post', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes, - mockState - ); + updateAttributes( linkSuggestion, setAttributes, mockState ); expect( mockState ).toEqual( { id: 1337, label: 'Menu Test', @@ -422,7 +367,7 @@ describe( 'edit', () => { url: 'https://wordpress.local/menu-test/', } ); // Click on the existing link control, and toggle opens new tab. - updateNavigationLinkBlockAttributes( + updateAttributes( { url: 'https://wordpress.local/menu-test/', opensInNewTab: true, @@ -453,11 +398,7 @@ describe( 'edit', () => { type: 'post', }; - updateNavigationLinkBlockAttributes( - linkSuggestion, - setAttributes, - mockState - ); + updateAttributes( linkSuggestion, setAttributes, mockState ); expect( mockState ).toEqual( { id: 1337, label: 'Menu Test', @@ -467,7 +408,7 @@ describe( 'edit', () => { url: 'https://wordpress.local/menu-test/', } ); // Click on the existing link control, and toggle opens new tab. - updateNavigationLinkBlockAttributes( + updateAttributes( { url: 'https://wordpress.local/foo/', opensInNewTab: false, diff --git a/packages/block-library/src/navigation-link/update-attributes.js b/packages/block-library/src/navigation-link/update-attributes.js new file mode 100644 index 0000000000000..247498107ac35 --- /dev/null +++ b/packages/block-library/src/navigation-link/update-attributes.js @@ -0,0 +1,103 @@ +/** + * External dependencies + */ +import escapeHtml from 'escape-html'; + +/** + * WordPress dependencies + */ +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 } ), + } ); +}; From ee90c2b1df7cb59e1f6ea3e2aaeb3b15386396ac Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 24 Nov 2022 15:37:25 +0000 Subject: [PATCH 122/380] Navigation: Hide the create new menu button if the experiment is enabled (#46019) --- .../src/navigation/edit/navigation-menu-selector.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/navigation/edit/navigation-menu-selector.js b/packages/block-library/src/navigation/edit/navigation-menu-selector.js index 9817934c3a2b1..4b1182475e8d5 100644 --- a/packages/block-library/src/navigation/edit/navigation-menu-selector.js +++ b/packages/block-library/src/navigation/edit/navigation-menu-selector.js @@ -143,7 +143,11 @@ function NavigationMenuSelector( { }, }; - if ( ! hasNavigationMenus && ! hasClassicMenus ) { + if ( + ! hasNavigationMenus && + ! hasClassicMenus && + ! isOffCanvasNavigationEditorEnabled + ) { return ( + ) } + /> + +
    + + +
    + ) } + { isMobile && ( + + ) } + + ); +} + +function MediaTabNavigation( { onInsert, rootClientId, mediaCategories } ) { + return ( + + + + { mediaCategories.map( ( category ) => ( + + + { category.label } + + + + ) ) } + + + { mediaCategories.map( ( category ) => ( + + + { __( 'Back' ) } + + + + ) ) } + + ); +} + +export default MediaTab; diff --git a/packages/block-editor/src/components/inserter/media-tab/utils.js b/packages/block-editor/src/components/inserter/media-tab/utils.js new file mode 100644 index 0000000000000..f6a18514d728c --- /dev/null +++ b/packages/block-editor/src/components/inserter/media-tab/utils.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; + +const mediaTypeTag = { image: 'img', video: 'video', audio: 'audio' }; + +export function getBlockAndPreviewFromMedia( media, mediaType ) { + // Add the common attributes between the different media types. + const attributes = { + id: media.id, + }; + // Some props are named differently between the Media REST API and Media Library API. + // For example `source_url` is used in the former and `url` is used in the latter. + const mediaSrc = media.source_url || media.url; + const alt = media.alt_text || media.alt || undefined; + const caption = media.caption?.raw || media.caption; + if ( caption && typeof caption === 'string' ) { + attributes.caption = caption; + } + if ( mediaType === 'image' ) { + attributes.url = mediaSrc; + attributes.alt = alt; + } else if ( [ 'video', 'audio' ].includes( mediaType ) ) { + attributes.src = mediaSrc; + } + const PreviewTag = mediaTypeTag[ mediaType ]; + const preview = ( + + ); + return [ createBlock( `core/${ mediaType }`, attributes ), preview ]; +} diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index c6d019c8ff9a7..37a09dd448870 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -28,6 +28,7 @@ import BlockPatternsTabs, { BlockPatternsCategoryDialog, } from './block-patterns-tab'; import ReusableBlocksTab from './reusable-blocks-tab'; +import { MediaTab, MediaCategoryDialog, useMediaCategories } from './media-tab'; import InserterSearchResults from './search-results'; import useInsertionPoint from './hooks/use-insertion-point'; import InserterTabs from './tabs'; @@ -54,6 +55,8 @@ function InserterMenu( const [ hoveredItem, setHoveredItem ] = useState( null ); const [ selectedPatternCategory, setSelectedPatternCategory ] = useState( null ); + const [ selectedMediaCategory, setSelectedMediaCategory ] = + useState( null ); const [ selectedTab, setSelectedTab ] = useState( null ); const [ destinationRootClientId, onInsertBlocks, onToggleInsertionPoint ] = @@ -68,7 +71,6 @@ function InserterMenu( ( select ) => { const { __experimentalGetAllowedPatterns, getSettings } = select( blockEditorStore ); - return { showPatterns: !! __experimentalGetAllowedPatterns( destinationRootClientId @@ -80,6 +82,9 @@ function InserterMenu( [ destinationRootClientId ] ); + const mediaCategories = useMediaCategories( destinationRootClientId ); + const showMedia = !! mediaCategories.length; + const onInsert = useCallback( ( blocks, meta, shouldForceFocusBlock ) => { onInsertBlocks( blocks, meta, shouldForceFocusBlock ); @@ -170,16 +175,36 @@ function InserterMenu( [ destinationRootClientId, onInsert, onHover ] ); + const mediaTab = useMemo( + () => ( + + ), + [ + destinationRootClientId, + onInsert, + selectedMediaCategory, + setSelectedMediaCategory, + ] + ); + const getCurrentTab = useCallback( ( tab ) => { if ( tab.name === 'blocks' ) { return blocksTab; } else if ( tab.name === 'patterns' ) { return patternsTab; + } else if ( tab.name === 'reusable' ) { + return reusableBlocksTab; + } else if ( tab.name === 'media' ) { + return mediaTab; } - return reusableBlocksTab; }, - [ blocksTab, patternsTab, reusableBlocksTab ] + [ blocksTab, patternsTab, reusableBlocksTab, mediaTab ] ); const searchRef = useRef(); @@ -191,8 +216,10 @@ function InserterMenu( const showPatternPanel = selectedTab === 'patterns' && ! filterValue && selectedPatternCategory; - const showAsTabs = ! filterValue && ( showPatterns || hasReusableBlocks ); - + const showAsTabs = + ! filterValue && ( showPatterns || hasReusableBlocks || showMedia ); + const showMediaPanel = + selectedTab === 'media' && ! filterValue && selectedMediaCategory; return (
    @@ -244,6 +272,13 @@ function InserterMenu(
    ) }
    + { showMediaPanel && ( + + ) } { showInserterHelpPanel && hoveredItem && ( ) } diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index ad5a2d6642b4a..dc1036a5b5c26 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -480,3 +480,151 @@ $block-inserter-tabs-height: 44px; .block-editor-inserter__patterns-category-panel-title { font-size: calc(1.25 * 13px); } + +.block-editor-inserter__media-tabs-container { + height: 100%; + + nav { + height: 100%; + } + + .block-editor-inserter__media-library-button { + padding: $grid-unit-20; + justify-content: center; + margin-top: $grid-unit-20; + width: 100%; + } +} + +.block-editor-inserter__media-tabs { + display: flex; + flex-direction: column; + padding: $grid-unit-20; + overflow-y: auto; + height: 100%; + + // Push the listitem wrapping the "open media library" button to the bottom of the panel. + div[role="listitem"]:last-child { + margin-top: auto; + } + + &__media-category { + &.is-selected { + color: var(--wp-admin-theme-color); + position: relative; + + .components-flex-item { + filter: brightness(0.95); + } + + svg { + fill: var(--wp-admin-theme-color); + } + + &::after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + border-radius: $radius-block-ui; + opacity: 0.04; + background: var(--wp-admin-theme-color); + } + } + } +} + +.block-editor-inserter__media-dialog { + background: $gray-100; + border-left: $border-width solid $gray-200; + border-right: $border-width solid $gray-200; + position: absolute; + padding: $grid-unit-20 $grid-unit-30; + top: 0; + left: 0; + height: 100%; + width: 100%; + overflow-y: auto; + scrollbar-gutter: stable both-edges; + + @include break-medium { + left: 100%; + display: block; + width: 300px; + } + + .block-editor-block-preview__container { + box-shadow: 0 15px 25px rgb(0 0 0 / 7%); + &:hover { + box-shadow: 0 0 0 2px $gray-900, 0 15px 25px rgb(0 0 0 / 7%); + } + } + + .block-editor-inserter__media-panel { + height: 100%; + + &-title { + font-size: calc(1.25 * 13px); + } + + &-spinner { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + + &-search { + &.components-search-control { + input[type="search"].components-search-control__input { + background: $white; + } + } + } + } +} + +.block-editor-inserter__media-list { + margin-top: $grid-unit-20; + &__list-item { + cursor: pointer; + margin-bottom: $grid-unit-30; + + &.is-placeholder { + min-height: 100px; + } + + &[draggable="true"] .block-editor-block-preview__container { + cursor: grab; + } + } + + &__item { + height: 100%; + + &-preview { + display: flex; + align-items: center; + overflow: hidden; + border-radius: 4px; + + > * { + margin: 0 auto; + max-width: 100%; + } + } + + &:hover &-preview { + box-shadow: 0 0 0 2px var(--wp-admin-theme-color); + } + + &:focus &-preview { + box-shadow: inset 0 0 0 1px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + } + } +} diff --git a/packages/block-editor/src/components/inserter/tabs.js b/packages/block-editor/src/components/inserter/tabs.js index 24bfade8b2f4b..8a80e96cb78cc 100644 --- a/packages/block-editor/src/components/inserter/tabs.js +++ b/packages/block-editor/src/components/inserter/tabs.js @@ -22,11 +22,17 @@ const reusableBlocksTab = { title: __( 'Reusable' ), icon: reusableBlockIcon, }; +const mediaTab = { + name: 'media', + /* translators: Media tab title in the block inserter. */ + title: __( 'Media' ), +}; function InserterTabs( { children, showPatterns = false, showReusableBlocks = false, + showMedia = false, onSelect, prioritizePatterns, } ) { @@ -39,10 +45,12 @@ function InserterTabs( { if ( ! prioritizePatterns && showPatterns ) { tempTabs.push( patternsTab ); } + if ( showMedia ) { + tempTabs.push( mediaTab ); + } if ( showReusableBlocks ) { tempTabs.push( reusableBlocksTab ); } - return tempTabs; }, [ prioritizePatterns, @@ -50,6 +58,7 @@ function InserterTabs( { showPatterns, patternsTab, showReusableBlocks, + showMedia, reusableBlocksTab, ] ); diff --git a/packages/compose/src/hooks/use-focus-outside/index.ts b/packages/compose/src/hooks/use-focus-outside/index.ts index cbf1d45d8c9e4..0d62318d38eca 100644 --- a/packages/compose/src/hooks/use-focus-outside/index.ts +++ b/packages/compose/src/hooks/use-focus-outside/index.ts @@ -151,6 +151,23 @@ export default function useFocusOutside( return; } + // The usage of this attribute should be avoided. The only use case + // would be when we load modals that are not React components and + // therefore don't exist in the React tree. An example is opening + // the Media Library modal from another dialog. + // This attribute should contain a selector of the related target + // we want to ignore, because we still need to trigger the blur event + // on all other cases. + const ignoreForRelatedTarget = event.target.getAttribute( + 'data-unstable-ignore-focus-outside-for-relatedtarget' + ); + if ( + ignoreForRelatedTarget && + event.relatedTarget?.closest( ignoreForRelatedTarget ) + ) { + return; + } + blurCheckTimeoutId.current = setTimeout( () => { // If document is not focused then focus should remain // inside the wrapped component and therefore we cancel diff --git a/packages/core-data/src/fetch/fetch-media.js b/packages/core-data/src/fetch/fetch-media.js new file mode 100644 index 0000000000000..c7a3a429a8627 --- /dev/null +++ b/packages/core-data/src/fetch/fetch-media.js @@ -0,0 +1,13 @@ +/** + * WordPress dependencies + */ +import { resolveSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { STORE_NAME as coreStore } from '../name'; + +export default async function fetchMedia( settings = {} ) { + return resolveSelect( coreStore ).getMediaItems( settings ); +} diff --git a/packages/core-data/src/fetch/index.js b/packages/core-data/src/fetch/index.js index 8d4d28e3b0db8..2f765825f3e70 100644 --- a/packages/core-data/src/fetch/index.js +++ b/packages/core-data/src/fetch/index.js @@ -1,2 +1,3 @@ export { default as __experimentalFetchLinkSuggestions } from './__experimental-fetch-link-suggestions'; export { default as __experimentalFetchUrlData } from './__experimental-fetch-url-data'; +export { default as __experimentalFetchMedia } from './fetch-media'; diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index 0f42214b45442..f990ee7f15f69 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -8,7 +8,11 @@ import classnames from 'classnames'; */ import { useSelect, useDispatch } from '@wordpress/data'; import { useCallback, useMemo, useRef, Fragment } from '@wordpress/element'; -import { useEntityBlockEditor, store as coreStore } from '@wordpress/core-data'; +import { + useEntityBlockEditor, + __experimentalFetchMedia as fetchMedia, + store as coreStore, +} from '@wordpress/core-data'; import { BlockList, BlockEditorProvider, @@ -131,6 +135,7 @@ export default function BlockEditor( { setIsInserterOpen } ) { return { ...restStoredSettings, + __unstableFetchMedia: fetchMedia, __experimentalBlockPatterns: blockPatterns, __experimentalBlockPatternCategories: blockPatternCategories, }; diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 0b69dcc66eb81..69b4f9b42cfc6 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -7,6 +7,7 @@ import { store as coreStore, __experimentalFetchLinkSuggestions as fetchLinkSuggestions, __experimentalFetchUrlData as fetchUrlData, + __experimentalFetchMedia as fetchMedia, } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; @@ -182,6 +183,9 @@ function useBlockEditorSettings( settings, hasTemplate ) { __experimentalBlockPatternCategories: blockPatternCategories, __experimentalFetchLinkSuggestions: ( search, searchOptions ) => fetchLinkSuggestions( search, searchOptions, settings ), + // TODO: We should find a proper way to consolidate similar cases + // like reusable blocks, fetch entities, etc. + __unstableFetchMedia: fetchMedia, __experimentalFetchRichUrlData: fetchUrlData, __experimentalCanUserUseUnfilteredHTML: canUseUnfilteredHTML, __experimentalUndo: undo, diff --git a/test/e2e/specs/editor/various/inserting-blocks.spec.js b/test/e2e/specs/editor/various/inserting-blocks.spec.js index cc75245d48bac..f45a911650823 100644 --- a/test/e2e/specs/editor/various/inserting-blocks.spec.js +++ b/test/e2e/specs/editor/various/inserting-blocks.spec.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +const path = require( 'path' ); + /** * WordPress dependencies */ @@ -286,6 +291,50 @@ test.describe( 'Inserting blocks (@firefox, @webkit)', () => { } ); } ); +test.describe( 'insert media from inserter', () => { + let uploadedMedia; + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.deleteAllMedia(); + uploadedMedia = await requestUtils.uploadMedia( + path.resolve( + process.cwd(), + 'test/e2e/assets/10x10_e2e_test_image_z9T8jK.png' + ) + ); + } ); + test.afterAll( async ( { requestUtils } ) => { + Promise.all( [ + requestUtils.deleteAllMedia(), + requestUtils.deleteAllPosts(), + ] ); + } ); + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + test( 'insert media from the global inserter', async ( { + page, + editor, + } ) => { + await page.click( + 'role=region[name="Editor top bar"i] >> role=button[name="Toggle block inserter"i]' + ); + await page.click( + 'role=region[name="Block Library"i] >> role=tab[name="Media"i]' + ); + await page.click( + '[aria-label="Media categories"i] >> role=button[name="Images"i]' + ); + await page.click( + `role=listbox[name="Media List"i] >> role=option[name="${ uploadedMedia.title.raw }"]` + ); + await expect.poll( editor.getEditedPostContent ).toBe( + ` +
    ${ uploadedMedia.alt_text }
    +` + ); + } ); +} ); + class InsertingBlocksUtils { constructor( { page, editor } ) { this.page = page; From 362a6c40d8c66cef6ef1f912602993fb7aa86ac5 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 25 Nov 2022 13:37:25 +0000 Subject: [PATCH 137/380] Limit back to Nav block and always skip to Nav block (#46037) --- .../block-editor/src/components/block-card/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index 622d13f2c278d..dab832861ae66 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -30,15 +30,16 @@ function BlockCard( { title, icon, description, blockType, className } ) { const isOffCanvasNavigationEditorEnabled = window?.__experimentalEnableOffCanvasNavigationEditor === true; - const { parentBlockClientId } = useSelect( ( select ) => { - const { getSelectedBlockClientId, getBlockParents } = + const { parentNavBlockClientId } = useSelect( ( select ) => { + const { getSelectedBlockClientId, getBlockParentsByBlockName } = select( blockEditorStore ); const _selectedBlockClientId = getSelectedBlockClientId(); return { - parentBlockClientId: getBlockParents( + parentNavBlockClientId: getBlockParentsByBlockName( _selectedBlockClientId, + 'core/navigation', true )[ 0 ], }; @@ -48,10 +49,10 @@ function BlockCard( { title, icon, description, blockType, className } ) { return (
    - { isOffCanvasNavigationEditorEnabled && parentBlockClientId && ( + { isOffCanvasNavigationEditorEnabled && parentNavBlockClientId && ( + + + + + ) : ( + + { label } + + ) } + { hasChildren && ( + <> + { ! context.openSubmenusOnClick && + context.showSubmenuIcon && ( + + ) } +
      + +
    + + ) } + + ); +} diff --git a/packages/block-library/src/page-list-item/index.js b/packages/block-library/src/page-list-item/index.js new file mode 100644 index 0000000000000..18b7fe2e40e9e --- /dev/null +++ b/packages/block-library/src/page-list-item/index.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { pages as icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import initBlock from '../utils/init-block'; +import metadata from './block.json'; +import edit from './edit.js'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + __experimentalLabel: ( { label } ) => label, + icon, + example: {}, + edit, +}; + +export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/page-list-item/init.js b/packages/block-library/src/page-list-item/init.js new file mode 100644 index 0000000000000..79f0492c2cb2f --- /dev/null +++ b/packages/block-library/src/page-list-item/init.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import { init } from './'; + +export default init(); diff --git a/packages/block-library/src/page-list/edit.js b/packages/block-library/src/page-list/edit.js index ac7ed6162cf97..d9e541305b697 100644 --- a/packages/block-library/src/page-list/edit.js +++ b/packages/block-library/src/page-list/edit.js @@ -10,6 +10,7 @@ import { InspectorControls, BlockControls, useBlockProps, + useInnerBlocksProps, getColorClassName, } from '@wordpress/block-editor'; import { @@ -20,15 +21,13 @@ import { ComboboxControl, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useMemo, useState, memo } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; -import { store as coreStore, useEntityRecords } from '@wordpress/core-data'; +import { useMemo, useState } from '@wordpress/element'; +import { useEntityRecords } from '@wordpress/core-data'; /** * Internal dependencies */ import ConvertToLinksModal from './convert-to-links-modal'; -import { ItemSubmenuIcon } from '../navigation-link/icons'; // We only show the edit option when page count is <= MAX_PAGE_COUNT // Performance of Navigation Links is not good past this value. @@ -65,6 +64,39 @@ export default function PageListEdit( { style: { ...context.style?.color }, } ); + const makeBlockTemplate = ( parentId = 0 ) => { + const pages = pagesByParentId.get( parentId ); + + if ( ! pages?.length ) { + return []; + } + + return pages.reduce( ( template, page ) => { + const hasChildren = pagesByParentId.has( page.id ); + const pageProps = { + id: page.id, + label: page.title?.rendered, + title: page.title?.rendered, + link: page.url, + hasChildren, + }; + let item = null; + const children = makeBlockTemplate( page.id ); + item = [ 'core/page-list-item', pageProps, children ]; + + template.push( item ); + + return template; + }, [] ); + }; + + const pagesTemplate = useMemo( makeBlockTemplate, [ pagesByParentId ] ); + + const innerBlocksProps = useInnerBlocksProps( blockProps, { + template: pagesTemplate, + templateLock: 'all', + } ); + const getBlockContent = () => { if ( ! hasResolvedPages ) { return ( @@ -95,15 +127,7 @@ export default function PageListEdit( { } if ( totalPages > 0 ) { - return ( -
      - -
    - ); + return
      ; } }; @@ -155,21 +179,6 @@ export default function PageListEdit( { ); } -function useFrontPageId() { - return useSelect( ( select ) => { - const canReadSettings = select( coreStore ).canUser( - 'read', - 'settings' - ); - if ( ! canReadSettings ) { - return undefined; - } - - const site = select( coreStore ).getEntityRecord( 'root', 'site' ); - return site?.show_on_front === 'page' && site?.page_on_front; - }, [] ); -} - function useGetPages() { const { records: pages, hasResolved: hasResolvedPages } = useEntityRecords( 'postType', @@ -221,92 +230,3 @@ function usePageData( pageId = 0 ) { }; }, [ pageId, pages, hasResolvedPages ] ); } - -const PageItems = memo( function PageItems( { - context, - pagesByParentId, - parentId = 0, - depth = 0, -} ) { - const parentPage = usePageData( parentId ); - const pages = pagesByParentId.get( parentId ) - ? pagesByParentId.get( parentId ) - : [ parentPage ]; - const frontPageId = useFrontPageId(); - - if ( ! pages?.length ) { - return []; - } - - return pages.map( ( page ) => { - const hasChildren = pagesByParentId.has( page.id ); - const isNavigationChild = 'showSubmenuIcon' in context; - return ( -
    • - { hasChildren && context.openSubmenusOnClick ? ( - <> - - - - - - ) : ( - - { page.title?.rendered } - - ) } - { hasChildren && ( - <> - { ! context.openSubmenusOnClick && - context.showSubmenuIcon && ( - - ) } -
        - -
      - - ) } -
    • - ); - } ); -} ); diff --git a/test/integration/fixtures/blocks/core__page-list-item.html b/test/integration/fixtures/blocks/core__page-list-item.html new file mode 100644 index 0000000000000..2b1b33d71231c --- /dev/null +++ b/test/integration/fixtures/blocks/core__page-list-item.html @@ -0,0 +1 @@ + diff --git a/test/integration/fixtures/blocks/core__page-list-item.json b/test/integration/fixtures/blocks/core__page-list-item.json new file mode 100644 index 0000000000000..5e6f09eb971eb --- /dev/null +++ b/test/integration/fixtures/blocks/core__page-list-item.json @@ -0,0 +1,11 @@ +[ + { + "name": "core/page-list-item", + "isValid": true, + "attributes": { + "label": "Page", + "link": "https://example.com" + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__page-list-item.parsed.json b/test/integration/fixtures/blocks/core__page-list-item.parsed.json new file mode 100644 index 0000000000000..e93cff97dd385 --- /dev/null +++ b/test/integration/fixtures/blocks/core__page-list-item.parsed.json @@ -0,0 +1,14 @@ +[ + { + "blockName": "core/page-list-item", + "attrs": { + "id": "1", + "label": "Page", + "link": "https://example.com", + "hasChildren": "false" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } +] diff --git a/test/integration/fixtures/blocks/core__page-list-item.serialized.html b/test/integration/fixtures/blocks/core__page-list-item.serialized.html new file mode 100644 index 0000000000000..d61659fe1899a --- /dev/null +++ b/test/integration/fixtures/blocks/core__page-list-item.serialized.html @@ -0,0 +1 @@ + From 0f6bfbb461bfe44bfec474c7b66547485451f43f Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Fri, 25 Nov 2022 17:45:30 +0000 Subject: [PATCH 140/380] Bump plugin version to 14.6.1 --- gutenberg.php | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 3c45ea942c917..0082600db8620 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the new block editor in core. * Requires at least: 5.9 * Requires PHP: 5.6 - * Version: 14.6.0 + * Version: 14.6.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index daeab8f712fc1..31d7463938053 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "14.6.0", + "version": "14.6.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 759a069c6e966..7681db5a97551 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "14.6.0", + "version": "14.6.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 0a1b333ae1c3a66f160965fd8000e150aebd87dd Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Fri, 25 Nov 2022 18:05:16 +0000 Subject: [PATCH 141/380] Update Changelog for 14.6.1 --- changelog.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/changelog.txt b/changelog.txt index e0ff8b9afe40d..3a8c82ca84c2c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,26 @@ == Changelog == += 14.6.1 = + +## Changelog + +### Fixes + +#### Global Styles +- Fix the `upgrader_process_complete` hook for `wp_theme_has_theme_json`. ([45881](https://github.com/WordPress/gutenberg/pull/45881)) +- Update `wp_theme_has_theme_json` to use `WP_Object_Cache`. ([45543](https://github.com/WordPress/gutenberg/pull/45543)) + +### Bug Fixes +- Tag Processor: Prevent bugs from pre-PHP8 strspn/strcspn behavior. ([45822](https://github.com/WordPress/gutenberg/pull/45822)) + +## Contributors + +The following contributors merged PRs in this release: + +@oandregal @dmsnell + + + = 14.6.0 = ## Changelog From 40aa5e62ed7f517c1d45548227d4e1845aae850a Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 26 Nov 2022 00:31:49 +0100 Subject: [PATCH 142/380] `NumberControl`: refactor styles/tests/stories to TypeScript, replace `fireEvent` with `user-event` (#45990) * Rename files * Fix Storybook example types * Rename and type styles * Avoid unnecessary wrapping of `BaseNumberControl` * Type `getInput` * Fix more minor type errors * Use `toBeInTheDocument` when possible * Replace `fireEvent` with `user-event` * Use `toHaveValue` instead of accessing `input.value` directly` * Update failing tests to make them pass using `user-event` * Alternative blur method * CHANGELOG * Fix wrong comment * Adding missing Storybook config * `toBeInTheDocument()` => `toBeVisible()` * Remove `getInput()`, replace with inline query * Remove redundant `={true}` * Refine spinbutton expected onChange argument * Remove duplicate test * Improve test wording --- packages/components/CHANGELOG.md | 4 + .../stories/{index.js => index.tsx} | 31 +- ...rol-styles.js => number-control-styles.ts} | 8 +- .../src/number-control/test/index.js | 478 -------------- .../src/number-control/test/index.tsx | 603 ++++++++++++++++++ 5 files changed, 636 insertions(+), 488 deletions(-) rename packages/components/src/number-control/stories/{index.js => index.tsx} (56%) rename packages/components/src/number-control/styles/{number-control-styles.js => number-control-styles.ts} (81%) delete mode 100644 packages/components/src/number-control/test/index.js create mode 100644 packages/components/src/number-control/test/index.tsx diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index c57f3b01d5825..f46afbb1d1898 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -6,6 +6,10 @@ - `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). +### Internal + +- NumberControl: refactor styles/tests/stories to TypeScript, replace fireEvent with user-event ([#45990](https://github.com/WordPress/gutenberg/pull/45990)). + ## 22.1.0 (2022-11-16) ### Enhancements diff --git a/packages/components/src/number-control/stories/index.js b/packages/components/src/number-control/stories/index.tsx similarity index 56% rename from packages/components/src/number-control/stories/index.js rename to packages/components/src/number-control/stories/index.tsx index 4daff7e34b2ba..8dce45db26d5d 100644 --- a/packages/components/src/number-control/stories/index.js +++ b/packages/components/src/number-control/stories/index.tsx @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + /** * WordPress dependencies */ @@ -6,9 +11,9 @@ import { useState } from '@wordpress/element'; /** * Internal dependencies */ -import NumberControl from '../'; +import NumberControl from '..'; -export default { +const meta: ComponentMeta< typeof NumberControl > = { title: 'Components (Experimental)/NumberControl', component: NumberControl, argTypes: { @@ -19,10 +24,19 @@ export default { type: { control: { type: 'text' } }, value: { control: null }, }, + parameters: { + controls: { expanded: true }, + docs: { source: { state: 'open' } }, + }, }; -function Template( { onChange, ...props } ) { - const [ value, setValue ] = useState( '0' ); +export default meta; + +const Template: ComponentStory< typeof NumberControl > = ( { + onChange, + ...props +} ) => { + const [ value, setValue ] = useState< string | undefined >( '0' ); const [ isValidValue, setIsValidValue ] = useState( true ); return ( @@ -32,14 +46,17 @@ function Template( { onChange, ...props } ) { value={ value } onChange={ ( v, extra ) => { setValue( v ); - setIsValidValue( extra.event.target.validity.valid ); - onChange( v, extra ); + setIsValidValue( + ( extra.event.target as HTMLInputElement ).validity + .valid + ); + onChange?.( v, extra ); } } />

      Is valid? { isValidValue ? 'Yes' : 'No' }

      ); -} +}; export const Default = Template.bind( {} ); Default.args = { diff --git a/packages/components/src/number-control/styles/number-control-styles.js b/packages/components/src/number-control/styles/number-control-styles.ts similarity index 81% rename from packages/components/src/number-control/styles/number-control-styles.js rename to packages/components/src/number-control/styles/number-control-styles.ts index 4f0bcb91bb8dd..dfc6171cf411b 100644 --- a/packages/components/src/number-control/styles/number-control-styles.js +++ b/packages/components/src/number-control/styles/number-control-styles.ts @@ -1,4 +1,3 @@ -// @ts-nocheck /** * External dependencies */ @@ -12,8 +11,9 @@ import InputControl from '../../input-control'; import { COLORS } from '../../utils'; import Button from '../../button'; import { space } from '../../ui/utils/space'; +import type { NumberControlProps } from '../types'; -const htmlArrowStyles = ( { hideHTMLArrows } ) => { +const htmlArrowStyles = ( { hideHTMLArrows }: { hideHTMLArrows: boolean } ) => { if ( ! hideHTMLArrows ) { return ``; } @@ -35,7 +35,9 @@ export const Input = styled( InputControl )` ${ htmlArrowStyles }; `; -const spinButtonSizeStyles = ( { size } ) => { +const spinButtonSizeStyles = ( { + size, +}: Pick< NumberControlProps, 'size' > ) => { if ( size !== 'small' ) { return ``; } diff --git a/packages/components/src/number-control/test/index.js b/packages/components/src/number-control/test/index.js deleted file mode 100644 index b1a57232ef275..0000000000000 --- a/packages/components/src/number-control/test/index.js +++ /dev/null @@ -1,478 +0,0 @@ -/** - * External dependencies - */ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -/** - * WordPress dependencies - */ -import { useState } from '@wordpress/element'; -import { UP, DOWN, ENTER } from '@wordpress/keycodes'; - -/** - * Internal dependencies - */ -import BaseNumberControl from '../'; - -const getInput = () => screen.getByTestId( 'input' ); - -const fireKeyDown = ( data ) => - fireEvent.keyDown( document.activeElement || document.body, data ); - -const NumberControl = ( props ) => ( - -); - -function StatefulNumberControl( props ) { - const [ value, setValue ] = useState( props.value ); - const handleOnChange = ( v ) => setValue( v ); - - return ( - - ); -} - -describe( 'NumberControl', () => { - describe( 'Basic rendering', () => { - it( 'should render', () => { - render( ); - expect( getInput() ).not.toBeNull(); - } ); - - it( 'should render custom className', () => { - render( ); - expect( getInput() ).toBeTruthy(); - } ); - } ); - - describe( 'onChange handling', () => { - it( 'should provide onChange callback with number value', () => { - const spy = jest.fn(); - - render( - spy( v ) } /> - ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: 10 } } ); - - expect( spy ).toHaveBeenCalledWith( '10' ); - } ); - - it( 'should call onChange callback when value is clamped on blur', async () => { - const spy = jest.fn(); - render( - spy( v ) } - /> - ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: 1 } } ); - - // Before blurring, the value is still un-clamped - expect( input.value ).toBe( '1' ); - - input.blur(); - - // After blur, value is clamped - expect( input.value ).toBe( '4' ); - - // After the blur, the `onChange` callback fires asynchronously. - await waitFor( () => { - expect( spy ).toHaveBeenCalledTimes( 2 ); - } ); - - expect( spy ).toHaveBeenNthCalledWith( 1, '1' ); - expect( spy ).toHaveBeenNthCalledWith( 2, 4 ); - } ); - - it( 'should call onChange callback when value is not valid', () => { - const spy = jest.fn(); - render( - - spy( v, extra.event.target.validity.valid ) - } - /> - ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: 14 } } ); - - expect( input.value ).toBe( '14' ); - - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '10' ); - - expect( spy ).toHaveBeenCalledTimes( 2 ); - - // First call: invalid, unclamped value - expect( spy ).toHaveBeenNthCalledWith( 1, '14', false ); - // Second call: valid, clamped value - expect( spy ).toHaveBeenNthCalledWith( 2, 10, true ); - } ); - } ); - - describe( 'Validation', () => { - it( 'should clamp value within range on ENTER keypress', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: -100 } } ); - fireKeyDown( { keyCode: ENTER } ); - - /** - * This is zero because the value has been adjusted to - * respect the min/max range of the input. - */ - - expect( input.value ).toBe( '0' ); - } ); - - it( 'should clamp value within range on blur', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: 41 } } ); - - // Before blurring, the value is still un-clamped - expect( input.value ).toBe( '41' ); - - input.blur(); - - // After blur, value is clamped - expect( input.value ).toBe( '10' ); - } ); - - it( 'should parse to number value on ENTER keypress when required', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: '10 abc' } } ); - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '0' ); - } ); - - it( 'should parse to empty string on ENTER keypress when not required', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: '10 abc' } } ); - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '' ); - } ); - - it( 'should accept empty string on ENTER keypress for optional field', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: '' } } ); - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '' ); - } ); - - it( 'should not enforce numerical value for empty string when required is omitted', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: '' } } ); - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '' ); - } ); - - it( 'should enforce numerical value for empty string when required', () => { - render( ); - - const input = getInput(); - input.focus(); - fireEvent.change( input, { target: { value: '' } } ); - fireKeyDown( { keyCode: ENTER } ); - - expect( input.value ).toBe( '0' ); - } ); - } ); - - describe( 'Key UP interactions', () => { - it( 'should fire onKeyDown callback', () => { - const spy = jest.fn(); - - render( ); - - getInput().focus(); - fireKeyDown( { keyCode: UP } ); - - expect( spy ).toHaveBeenCalled(); - } ); - - it( 'should increment by step on key UP press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP } ); - - expect( input.value ).toBe( '6' ); - } ); - - it( 'should increment from a negative value', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP } ); - - expect( input.value ).toBe( '-4' ); - } ); - - it( 'should increment while preserving the decimal value when `step` is “any”', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP } ); - - expect( input.value ).toBe( '867.5309' ); - } ); - - it( 'should increment by shiftStep on key UP + shift press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP, shiftKey: true } ); - - expect( input.value ).toBe( '20' ); - } ); - - it( 'should increment by shiftStep while preserving the decimal value when `step` is “any”', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP, shiftKey: true } ); - - expect( input.value ).toBe( '867.5309' ); - } ); - - it( 'should increment by custom shiftStep on key UP + shift press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP, shiftKey: true } ); - - expect( input.value ).toBe( '100' ); - } ); - - it( 'should increment but be limited by max on shiftStep', () => { - render( - - ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP, shiftKey: true } ); - - expect( input.value ).toBe( '99' ); - } ); - - it( 'should not increment by shiftStep if disabled', () => { - render( - - ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: UP, shiftKey: true } ); - - expect( input.value ).toBe( '6' ); - } ); - } ); - - describe( 'Key DOWN interactions', () => { - it( 'should fire onKeyDown callback', () => { - const spy = jest.fn(); - render( ); - - getInput().focus(); - fireKeyDown( { keyCode: DOWN } ); - - expect( spy ).toHaveBeenCalled(); - } ); - - it( 'should decrement by step on key DOWN press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN } ); - - expect( input.value ).toBe( '4' ); - } ); - - it( 'should decrement from a negative value', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN } ); - - expect( input.value ).toBe( '-6' ); - } ); - - it( 'should decrement while preserving the decimal value when `step` is “any”', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN } ); - - expect( input.value ).toBe( '867.5309' ); - } ); - - it( 'should decrement by shiftStep on key DOWN + shift press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - - expect( input.value ).toBe( '0' ); - } ); - - it( 'should decrement by shiftStep while preserving the decimal value when `step` is “any”', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - - expect( input.value ).toBe( '867.5309' ); - } ); - - it( 'should decrement by custom shiftStep on key DOWN + shift press', () => { - render( ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - - expect( input.value ).toBe( '-100' ); - } ); - - it( 'should decrement but be limited by min on shiftStep', () => { - render( - - ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - - expect( input.value ).toBe( '4' ); - } ); - - it( 'should not decrement by shiftStep if disabled', () => { - render( - - ); - - const input = getInput(); - input.focus(); - fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - - expect( input.value ).toBe( '4' ); - } ); - } ); - - describe( 'custom spin buttons', () => { - test.each( [ undefined, 'none', 'native' ] )( - 'should not appear when spinControls = %s', - ( spinControls ) => { - render( ); - expect( - screen.queryByLabelText( 'Increment' ) - ).not.toBeInTheDocument(); - expect( - screen.queryByLabelText( 'Decrement' ) - ).not.toBeInTheDocument(); - } - ); - - test.each( [ - [ 'up', '1', {} ], - [ 'up', '2', { value: '1' } ], - [ 'up', '12', { value: '10', step: '2' } ], - [ 'up', '10', { value: '10', max: '10' } ], - [ 'down', '-1', {} ], - [ 'down', '1', { value: '2' } ], - [ 'down', '10', { value: '12', step: '2' } ], - [ 'down', '10', { value: '10', min: '10' } ], - ] )( - 'should spin %s to %s when props = %o', - async ( direction, expectedValue, props ) => { - const user = userEvent.setup( { - advanceTimers: jest.advanceTimersByTime, - } ); - const onChange = jest.fn(); - render( - - ); - await user.click( - screen.getByLabelText( - direction === 'up' ? 'Increment' : 'Decrement' - ) - ); - expect( onChange ).toHaveBeenCalledWith( expectedValue, { - event: expect.anything(), - } ); - } - ); - } ); -} ); diff --git a/packages/components/src/number-control/test/index.tsx b/packages/components/src/number-control/test/index.tsx new file mode 100644 index 0000000000000..2eeef4c996ae6 --- /dev/null +++ b/packages/components/src/number-control/test/index.tsx @@ -0,0 +1,603 @@ +/** + * External dependencies + */ +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import NumberControl from '..'; +import type { NumberControlProps } from '../types'; + +function StatefulNumberControl( props: NumberControlProps ) { + const [ value, setValue ] = useState( props.value ); + const handleOnChange = ( v: string | undefined ) => setValue( v ); + + return ( + + ); +} + +describe( 'NumberControl', () => { + describe( 'Basic rendering', () => { + it( 'should render', () => { + render( ); + expect( screen.getByRole( 'spinbutton' ) ).toBeVisible(); + } ); + + it( 'should render custom className', () => { + render( ); + expect( screen.getByRole( 'spinbutton' ) ).toBeVisible(); + } ); + } ); + + describe( 'onChange handling', () => { + it( 'should provide onChange callback with number value', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const spy = jest.fn(); + + render( + spy( v ) } /> + ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.type( input, '10' ); + + expect( spy ).toHaveBeenCalledWith( '10' ); + } ); + + it( 'should call onChange callback when value is clamped on blur', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChangeSpy = jest.fn(); + + render( + + onChangeSpy( + v, + ( extra.event.target as HTMLInputElement ).validity + .valid + ) + } + /> + ); + + const input = screen.getByRole( 'spinbutton' ); + + await user.clear( input ); + await user.type( input, '1' ); + + // Before blurring, the value is still un-clamped + expect( input ).toHaveValue( 1 ); + + // Blur the input + await user.keyboard( '[Tab]' ); + + // After blur, value is clamped + expect( input ).toHaveValue( 4 ); + + // After the blur, the `onChange` callback fires asynchronously. + await waitFor( () => { + expect( onChangeSpy ).toHaveBeenCalledTimes( 3 ); + } ); + + // First call: clear the input + expect( onChangeSpy ).toHaveBeenNthCalledWith( 1, '', true ); + // Second call: type '1' + expect( onChangeSpy ).toHaveBeenNthCalledWith( 2, '1', false ); + // Third call: clamp value + expect( onChangeSpy ).toHaveBeenNthCalledWith( 3, 4, true ); + } ); + + it( 'should call onChange callback when value is not valid', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChangeSpy = jest.fn(); + + render( + + onChangeSpy( + v, + ( extra.event.target as HTMLInputElement ).validity + .valid + ) + } + /> + ); + + const input = screen.getByRole( 'spinbutton' ); + + await user.clear( input ); + await user.type( input, '14' ); + + expect( input ).toHaveValue( 14 ); + + await user.keyboard( '[Enter]' ); + + expect( input ).toHaveValue( 10 ); + + expect( onChangeSpy ).toHaveBeenCalledTimes( 4 ); + + // First call: clear value + expect( onChangeSpy ).toHaveBeenNthCalledWith( 1, '', true ); + // Second call: valid, unclamped value + expect( onChangeSpy ).toHaveBeenNthCalledWith( 2, '1', true ); + // Third call: invalid, unclamped value + expect( onChangeSpy ).toHaveBeenNthCalledWith( 3, '14', false ); + // Fourth call: valid, clamped value + expect( onChangeSpy ).toHaveBeenNthCalledWith( 4, 10, true ); + } ); + } ); + + describe( 'Validation', () => { + it( 'should clamp value within range on ENTER keypress', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + + await user.clear( input ); + await user.type( input, '-100' ); + await user.keyboard( '[Enter]' ); + + /** + * This is zero because the value has been adjusted to + * respect the min/max range of the input. + */ + expect( input ).toHaveValue( 0 ); + } ); + + it( 'should clamp value within range on blur', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.type( input, '41' ); + + // Before blurring, the value is still un-clamped + expect( input ).toHaveValue( 41 ); + + // Blur the input + await user.click( document.body ); + + // After blur, value is clamped + expect( input ).toHaveValue( 10 ); + } ); + + it( 'should parse non-numeric values to a number on ENTER keypress when required', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.type( input, 'abc' ); + await user.keyboard( '[Enter]' ); + + expect( input ).toHaveValue( 0 ); + } ); + + it( 'should parse non-numeric values to empty string on ENTER keypress when not required', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.type( input, 'abc' ); + await user.keyboard( '[Enter]' ); + + // Note: the `input` field may still visually show the invalid input, + // while internally parsing the value as an empty string. + // + // React Testing Library associates `null` to empty string types for + // numeric `input` elements + // (see https://github.com/testing-library/jest-dom/blob/v5.16.5/src/utils.js#L191-L192) + expect( input ).toHaveValue( null ); + } ); + + it( 'should not enforce numerical value for empty string when required is omitted', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.keyboard( '[Enter]' ); + + // React Testing Library associates `null` to empty string types for + // numeric `input` elements + // (see https://github.com/testing-library/jest-dom/blob/v5.16.5/src/utils.js#L191-L192) + expect( input ).toHaveValue( null ); + } ); + + it( 'should enforce numerical value for empty string when required', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.clear( input ); + await user.keyboard( '[Enter]' ); + + expect( input ).toHaveValue( 0 ); + } ); + } ); + + describe( 'Key UP interactions', () => { + it( 'should fire onKeyDown callback', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + const spy = jest.fn(); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowUp]' ); + + expect( spy ).toHaveBeenCalled(); + } ); + + it( 'should increment by step on key UP press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowUp]' ); + + expect( input ).toHaveValue( 6 ); + } ); + + it( 'should increment from a negative value', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowUp]' ); + + expect( input ).toHaveValue( -4 ); + } ); + + it( 'should increment while preserving the decimal value when `step` is “any”', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowUp]' ); + + expect( input ).toHaveValue( 867.5309 ); + } ); + + it( 'should increment by shiftStep on key UP + shift press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowUp]{/Shift}' ); + + expect( input ).toHaveValue( 20 ); + } ); + + it( 'should increment by shiftStep while preserving the decimal value when `step` is “any”', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowUp]{/Shift}' ); + + expect( input ).toHaveValue( 867.5309 ); + } ); + + it( 'should increment by custom shiftStep on key UP + shift press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowUp]{/Shift}' ); + + expect( input ).toHaveValue( 100 ); + } ); + + it( 'should increment but be limited by max on shiftStep', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowUp]{/Shift}' ); + + expect( input ).toHaveValue( 99 ); + } ); + + it( 'should not increment by shiftStep if disabled', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowUp]{/Shift}' ); + + expect( input ).toHaveValue( 6 ); + } ); + } ); + + describe( 'Key DOWN interactions', () => { + it( 'should fire onKeyDown callback', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const spy = jest.fn(); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowDown]' ); + + expect( spy ).toHaveBeenCalled(); + } ); + + it( 'should decrement by step on key DOWN press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowDown]' ); + + expect( input ).toHaveValue( 4 ); + } ); + + it( 'should decrement from a negative value', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowDown]' ); + + expect( input ).toHaveValue( -6 ); + } ); + + it( 'should decrement while preserving the decimal value when `step` is “any”', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '[ArrowDown]' ); + + expect( input ).toHaveValue( 867.5309 ); + } ); + + it( 'should decrement by shiftStep on key DOWN + shift press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowDown]{/Shift}' ); + + expect( input ).toHaveValue( 0 ); + } ); + + it( 'should decrement by shiftStep while preserving the decimal value when `step` is “any”', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowDown]{/Shift}' ); + + expect( input ).toHaveValue( 867.5309 ); + } ); + + it( 'should decrement by custom shiftStep on key DOWN + shift press', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowDown]{/Shift}' ); + + expect( input ).toHaveValue( -100 ); + } ); + + it( 'should decrement but be limited by min on shiftStep', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowDown]{/Shift}' ); + + expect( input ).toHaveValue( 4 ); + } ); + + it( 'should not decrement by shiftStep if disabled', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + ); + + const input = screen.getByRole( 'spinbutton' ); + await user.click( input ); + await user.keyboard( '{Shift>}[ArrowDown]{/Shift}' ); + + expect( input ).toHaveValue( 4 ); + } ); + } ); + + describe( 'custom spin buttons', () => { + test.each( [ + undefined, + 'none', + 'native', + ] as NumberControlProps[ 'spinControls' ][] )( + 'should not appear when spinControls = %s', + ( spinControls ) => { + render( ); + expect( + screen.queryByLabelText( 'Increment' ) + ).not.toBeInTheDocument(); + expect( + screen.queryByLabelText( 'Decrement' ) + ).not.toBeInTheDocument(); + } + ); + + test.each( [ + [ 'up', '1', {} ], + [ 'up', '2', { value: '1' } ], + [ 'up', '12', { value: '10', step: '2' } ], + [ 'up', '10', { value: '10', max: 10 } ], + [ 'down', '-1', {} ], + [ 'down', '1', { value: '2' } ], + [ 'down', '10', { value: '12', step: '2' } ], + [ 'down', '10', { value: '10', min: 10 } ], + ] )( + 'should spin %s to %s when props = %o', + async ( direction, expectedValue, props ) => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChange = jest.fn(); + render( + + ); + await user.click( + screen.getByLabelText( + direction === 'up' ? 'Increment' : 'Decrement' + ) + ); + expect( onChange ).toHaveBeenCalledWith( expectedValue, { + event: expect.objectContaining( { + target: expect.any( HTMLInputElement ), + type: expect.any( String ), + } ), + } ); + } + ); + } ); +} ); From 5292d8051ce923917b4842c2f5555cfe07e5a62d Mon Sep 17 00:00:00 2001 From: Marcelo Serpa <81248+fullofcaffeine@users.noreply.github.com> Date: Sat, 26 Nov 2022 01:11:46 -0600 Subject: [PATCH 143/380] (release.md) Document the special case of shipping point releases when a new release branch already exists (#46083) --- docs/contributors/code/release.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index 4ce35e189fd70..f463cb319f665 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -150,6 +150,8 @@ The method for point releases is nearly identical to the main Plugin release pro The point release should only contain the _specific commits_ required. To do this you should checkout the previous _minor_ stable (i.e. non-RC) release branch (e.g. `release/12.5`) locally and then cherry pick any commits that you require into that branch. +_IMPORTANT:_ If an RC already exists for a new version, you _need_ to cherry-pick the same commits in the respective release branch, as they will not be included automatically. E.g.: If you're about to release a new point-release for 12.5 and just cherry-picked into `release/12.5`, but 12.6.0-rc.1 is already out, then you need to cherry-pick the same commits into the `release/12.6` branch, or they won't be included in subsequent releases for 12.6! + The cherry-picking process can be automated with the [`npm run cherry-pick` script](/docs/contributors/code/auto-cherry-picking.md). You must also ensure that all PRs being included are assigned to the Github Milestone on which the point release is based. Bear in mind, that when PRs are _merged_ they are automatically assigned a milestone for the next _stable_ release. Therefore you will need to go back through each PR in Github and re-assign the Milestone. From 0b1998cf997d866bc1eca182fedb1abb42e337fd Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Sat, 26 Nov 2022 12:45:27 +0100 Subject: [PATCH 144/380] `ColorPalette`: show `Clear` button even when `colors` array is empty (#46001) * Render `CircularOptionPicker` when `colors` is an empty array * Fix Storybook errors when `colors` array is empty * Move `onChange` mock inside each test * Rename `test` to `it` * Extract color constants outside `describe` scope, rename variables, use less US-centric colors * Add test to avoid regressions on `Clear` button not showing when `colors` is an empty array * Split clear button test into two separate tests * CHANGELOG * Remove initial color logic in Storybook example --- packages/components/CHANGELOG.md | 4 + .../components/src/color-palette/index.tsx | 4 - .../src/color-palette/stories/index.tsx | 6 +- .../test/__snapshots__/index.tsx.snap | 8 +- .../src/color-palette/test/index.tsx | 104 ++++++++++++------ 5 files changed, 78 insertions(+), 48 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index f46afbb1d1898..22a08c2f0dd08 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Bug Fix + +- `ColorPalette`: show "Clear" button even when colors array is empty ([#46001](https://github.com/WordPress/gutenberg/pull/46001)). + ### Enhancements - `TabPanel`: Add ability to set icon only tab buttons ([#45005](https://github.com/WordPress/gutenberg/pull/45005)). diff --git a/packages/components/src/color-palette/index.tsx b/packages/components/src/color-palette/index.tsx index 4c36a8e56df83..5146e3c858198 100644 --- a/packages/components/src/color-palette/index.tsx +++ b/packages/components/src/color-palette/index.tsx @@ -84,10 +84,6 @@ function SinglePalette( { } ); }, [ colors, value, onChange, clearColor ] ); - if ( colors.length === 0 ) { - return null; - } - return ( = { title: 'Components/ColorPalette', @@ -43,10 +42,7 @@ const Template: ComponentStory< typeof ColorPalette > = ( { onChange, ...args } ) => { - const firstColor = - ( args.colors as ColorObject[] )[ 0 ].color || - ( args.colors as PaletteObject[] )[ 0 ].colors[ 0 ].color; - const [ color, setColor ] = useState< string | undefined >( firstColor ); + const [ color, setColor ] = useState< string | undefined >(); return ( diff --git a/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap b/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap index 573ff940ebf0d..d4ea4015c639f 100644 --- a/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/color-palette/test/__snapshots__/index.tsx.snap @@ -63,10 +63,10 @@ exports[`ColorPalette should allow disabling custom color picker 1`] = ` class="components-circular-option-picker__option-wrapper" >
      @@ -235,10 +235,10 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` class="components-circular-option-picker__option-wrapper" >
      diff --git a/packages/components/src/color-palette/test/index.tsx b/packages/components/src/color-palette/test/index.tsx index ce81b1241a5f4..2cd95174cb4ed 100644 --- a/packages/components/src/color-palette/test/index.tsx +++ b/packages/components/src/color-palette/test/index.tsx @@ -9,24 +9,21 @@ import userEvent from '@testing-library/user-event'; */ import ColorPalette from '..'; +const EXAMPLE_COLORS = [ + { name: 'red', color: '#f00' }, + { name: 'green', color: '#0f0' }, + { name: 'blue', color: '#00f' }, +]; +const INITIAL_COLOR = EXAMPLE_COLORS[ 0 ].color; + describe( 'ColorPalette', () => { - const colors = [ - { name: 'red', color: '#f00' }, - { name: 'white', color: '#fff' }, - { name: 'blue', color: '#00f' }, - ]; - const currentColor = '#f00'; - const onChange = jest.fn(); - - beforeEach( () => { - onChange.mockClear(); - } ); + it( 'should render a dynamic toolbar of colors', () => { + const onChange = jest.fn(); - test( 'should render a dynamic toolbar of colors', () => { const { container } = render( ); @@ -34,11 +31,13 @@ describe( 'ColorPalette', () => { expect( container ).toMatchSnapshot(); } ); - test( 'should render three color button options', () => { + it( 'should render three color button options', () => { + const onChange = jest.fn(); + render( ); @@ -48,15 +47,16 @@ describe( 'ColorPalette', () => { ).toHaveLength( 3 ); } ); - test( 'should call onClick on an active button with undefined', async () => { + it( 'should call onClick on an active button with undefined', async () => { const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime, } ); + const onChange = jest.fn(); render( ); @@ -69,19 +69,22 @@ describe( 'ColorPalette', () => { expect( onChange ).toHaveBeenCalledWith( undefined ); } ); - test( 'should call onClick on an inactive button', async () => { + it( 'should call onClick on an inactive button', async () => { const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime, } ); + const onChange = jest.fn(); render( ); + // Click the first unpressed button + // (i.e. a button representing a color that is not the current color) await user.click( screen.getAllByRole( 'button', { name: /^Color:/, @@ -89,19 +92,21 @@ describe( 'ColorPalette', () => { } )[ 0 ] ); + // Expect the green color to have been selected expect( onChange ).toHaveBeenCalledTimes( 1 ); - expect( onChange ).toHaveBeenCalledWith( '#fff', 1 ); + expect( onChange ).toHaveBeenCalledWith( EXAMPLE_COLORS[ 1 ].color, 1 ); } ); - test( 'should call onClick with undefined, when the clearButton onClick is triggered', async () => { + it( 'should call onClick with undefined, when the clearButton onClick is triggered', async () => { const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime, } ); + const onChange = jest.fn(); render( ); @@ -112,12 +117,14 @@ describe( 'ColorPalette', () => { expect( onChange ).toHaveBeenCalledWith( undefined ); } ); - test( 'should allow disabling custom color picker', () => { + it( 'should allow disabling custom color picker', () => { + const onChange = jest.fn(); + const { container } = render( ); @@ -125,15 +132,16 @@ describe( 'ColorPalette', () => { expect( container ).toMatchSnapshot(); } ); - test( 'should render dropdown and its content', async () => { + it( 'should render dropdown and its content', async () => { const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime, } ); + const onChange = jest.fn(); render( ); @@ -153,12 +161,38 @@ describe( 'ColorPalette', () => { expect( dropdownButton ).toBeVisible(); expect( - within( dropdownButton ).getByText( colors[ 0 ].name ) + within( dropdownButton ).getByText( EXAMPLE_COLORS[ 0 ].name ) ).toBeVisible(); expect( within( dropdownButton ).getByText( - colors[ 0 ].color.replace( '#', '' ) + EXAMPLE_COLORS[ 0 ].color.replace( '#', '' ) ) ).toBeVisible(); } ); + + it( 'should show the clear button by default', () => { + const onChange = jest.fn(); + + render( + + ); + + expect( + screen.getByRole( 'button', { name: 'Clear' } ) + ).toBeInTheDocument(); + } ); + + it( 'should show the clear button even when `colors` is an empty array', () => { + const onChange = jest.fn(); + + render( ); + + expect( + screen.getByRole( 'button', { name: 'Clear' } ) + ).toBeInTheDocument(); + } ); } ); From 4caa89368d66610c9fd5aed38ea803d60fd239d4 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:49:37 +0900 Subject: [PATCH 145/380] Bump caniuse-lite version (#46093) --- package-lock.json | 6 ++-- .../test/__snapshots__/build.js.snap | 32 +++++++++---------- .../test/__snapshots__/build.js.snap | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 31d7463938053..c539ceb114a26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29230,9 +29230,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001352", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz", - "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==" + "version": "1.0.30001434", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz", + "integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA==" }, "capital-case": { "version": "1.0.4", 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 3feaba84de033..0f918da3bb9ab 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' => 'cf268e19006bef774112'), 'fileB.js' => array('dependencies' => array('wp-token-list'), 'version' => '7f3970305cf0aecb54ab')); +" array('dependencies' => array('lodash', 'wp-blob'), 'version' => 'bf200ecb3dcb6881a1f3'), 'fileB.js' => array('dependencies' => array('wp-token-list'), 'version' => '0af6c51a8e6ac934b85a')); " `; @@ -32,7 +32,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`dynamic-import\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('wp-blob'), 'version' => 'c8be4fceac30d1d00ca7'); +" array('wp-blob'), 'version' => '782d84ec5d7303bb6bd2'); " `; @@ -50,7 +50,7 @@ Array [ `; 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' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); " `; @@ -73,7 +73,7 @@ Array [ `; 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' => '49dba68ef238f954b358'); +" array('lodash', 'wp-blob'), 'version' => 'dabeb91f3cb9dd73d48d'); " `; @@ -96,21 +96,21 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`no-default\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array(), 'version' => 'f7e2cb527e601f74f8bd'); +" array(), 'version' => 'bb85a9737103c7054b00'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`no-default\` should produce expected output: External modules should match snapshot 1`] = `Array []`; exports[`DependencyExtractionWebpackPlugin Webpack \`no-deps\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array(), 'version' => '143ed23d4b8be5611fcb'); +" array(), 'version' => '091ffcd70d94dd16e773'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`no-deps\` should produce expected output: External modules should match snapshot 1`] = `Array []`; 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' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); " `; @@ -133,7 +133,7 @@ Array [ `; 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' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); " `; @@ -155,7 +155,7 @@ Array [ ] `; -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: Asset file 'main.asset.json' should match snapshot 1`] = `"{\\"dependencies\\":[\\"lodash\\"],\\"version\\":\\"a8f35bfc9f46482cc48a\\"}"`; exports[`DependencyExtractionWebpackPlugin Webpack \`output-format-json\` should produce expected output: External modules should match snapshot 1`] = ` Array [ @@ -168,7 +168,7 @@ Array [ `; 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' => '708c71445153f1d07e4a'); +" array('wp-blob', 'wp-script-handle-for-rxjs', 'wp-url'), 'version' => '2a29b245fc3d0509b5a8'); " `; @@ -207,17 +207,17 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'a.asset.php' should match snapshot 1`] = ` -" array('wp-blob'), 'version' => '09a0c551770a351c5ca7'); +" array('wp-blob'), 'version' => '4514ed711f6c035e0887'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'b.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => 'c9f00d690a9f72438910'); +" array('lodash', 'wp-blob'), 'version' => '168d32b5fb42f9e5d8ce'); " `; exports[`DependencyExtractionWebpackPlugin Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'runtime.asset.php' should match snapshot 1`] = ` -" array(), 'version' => '46ea0ff11ac53fa5e88b'); +" array(), 'version' => 'd3c2ce2cb84ff74b92e0'); " `; @@ -240,7 +240,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`style-imports\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => 'd8c0ee89d933a3809c0e'); +" array('lodash', 'wp-blob'), 'version' => '04b9da7eff6fbfcb0452'); " `; @@ -263,7 +263,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`wordpress\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '9b7ebe61044661fdabda'); +" array('lodash', 'wp-blob'), 'version' => '4c78134607e6ed966df3'); " `; @@ -286,7 +286,7 @@ Array [ `; exports[`DependencyExtractionWebpackPlugin Webpack \`wordpress-require\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('lodash', 'wp-blob'), 'version' => '40370eb4ce6428562da6'); +" array('lodash', 'wp-blob'), 'version' => 'ed2bd4e7df46768bb3c2'); " `; 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 ec163c00bd93d..6d93ca7e74f21 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`] = ` -"/******/ (() => { // webpackBootstrap +"/******/ (function() { // 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`] = ` -"/******/ (() => { // webpackBootstrap +"/******/ (function() { // webpackBootstrap var __webpack_exports__ = {}; function notMinified() { // eslint-disable-next-line no-console From d87548d19cbc3518da1da5be295bb89a4669d56a Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 28 Nov 2022 09:02:38 +0800 Subject: [PATCH 146/380] Nav Block: Move color controls to support panel (#46049) This will mean they appear under the styles tab when the block inspector tabs experiment is enabled. --- .../src/navigation/edit/index.js | 280 +++++++++--------- 1 file changed, 147 insertions(+), 133 deletions(-) diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 319b7f3736a3c..0a24063716fe0 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -20,11 +20,12 @@ import { __experimentalUseHasRecursion as useHasRecursion, store as blockEditorStore, withColors, - PanelColorSettings, ContrastChecker, getColorClassName, Warning, + __experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown, __experimentalUseBlockOverlayActive as useBlockOverlayActive, + __experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients, } from '@wordpress/block-editor'; import { EntityProvider, store as coreStore } from '@wordpress/core-data'; @@ -529,144 +530,157 @@ function Navigation( { } ); }, [ isDraftNavigationMenu, navigationMenu ] ); + const colorGradientSettings = useMultipleOriginColorsAndGradients(); const stylingInspectorControls = ( - - { hasSubmenuIndicatorSetting && ( - - { isResponsive && ( - <> - + { overlayMenuPreview && ( + ) } - - { overlayMenuPreview && ( - - ) } - - ) } -

      { __( 'Overlay Menu' ) }

      - ) } - onChange={ ( value ) => - setAttributes( { overlayMenu: value } ) - } - isBlock - hideLabelFromVision - > - - - - - { hasSubmenus && ( - <> -

      { __( 'Submenus' ) }

      - { - setAttributes( { - openSubmenusOnClick: value, - ...( value && { - showSubmenuIcon: true, - } ), // Make sure arrows are shown when we toggle this on. - } ); - } } - label={ __( 'Open on click' ) } - /> - - { - setAttributes( { - showSubmenuIcon: value, - } ); - } } - disabled={ attributes.openSubmenusOnClick } - label={ __( 'Show arrow' ) } +

      { __( 'Overlay Menu' ) }

      + + setAttributes( { overlayMenu: value } ) + } + isBlock + hideLabelFromVision + > + - - ) } -
      - ) } - { hasColorSettings && ( - - { enableContrastChecking && ( - <> - - - - ) } - - ) } -
      + + { hasSubmenus && ( + <> +

      { __( 'Submenus' ) }

      + { + setAttributes( { + openSubmenusOnClick: value, + ...( value && { + showSubmenuIcon: true, + } ), // Make sure arrows are shown when we toggle this on. + } ); + } } + label={ __( 'Open on click' ) } + /> + + { + setAttributes( { + showSubmenuIcon: value, + } ); + } } + disabled={ attributes.openSubmenusOnClick } + label={ __( 'Show arrow' ) } + /> + + ) } +
      + ) } +
      + + { hasColorSettings && ( + <> + setTextColor(), + }, + { + colorValue: backgroundColor.color, + label: __( 'Background' ), + onColorChange: setBackgroundColor, + resetAllFilter: () => setBackgroundColor(), + }, + { + colorValue: overlayTextColor.color, + label: __( 'Submenu & overlay text' ), + onColorChange: setOverlayTextColor, + resetAllFilter: () => setOverlayTextColor(), + }, + { + colorValue: overlayBackgroundColor.color, + label: __( 'Submenu & overlay background' ), + onColorChange: setOverlayBackgroundColor, + resetAllFilter: () => + setOverlayBackgroundColor(), + }, + ] } + panelId={ clientId } + { ...colorGradientSettings } + gradients={ [] } + disableCustomGradients={ true } + /> + { enableContrastChecking && ( + <> + + + + ) } + + ) } + + ); // If the block has inner blocks, but no menu id, then these blocks are either: From 21fc1d3afabd5eb63de104ac59dc25943796c00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20B=C3=A4thge?= Date: Mon, 28 Nov 2022 07:21:48 +0100 Subject: [PATCH 147/380] Fix invalid attribute markup in core/home-link block (#46089) The `core/home-link` block produces invalid HTML markup, after f4e4ac25151175ae229d1a2f4a0d6129f9738801 introduced a stray `"` in front of the `rel` attribute. This PR removes that wrong quotation mark. --- packages/block-library/src/home-link/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/home-link/index.php b/packages/block-library/src/home-link/index.php index 5bf7aeda5505d..33f41057c936b 100644 --- a/packages/block-library/src/home-link/index.php +++ b/packages/block-library/src/home-link/index.php @@ -128,7 +128,7 @@ function render_block_core_home_link( $attributes, $content, $block ) { $aria_current = is_home() || ( is_front_page() && 'page' === get_option( 'show_on_front' ) ) ? ' aria-current="page"' : ''; return sprintf( - '
    • %4$s
    • ', + '
    • %4$s
    • ', block_core_home_link_build_li_wrapper_attributes( $block->context ), esc_url( home_url() ), $aria_current, From a4eab09ba375fc65fab2f53950fa962bf221b6c5 Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Mon, 28 Nov 2022 09:21:44 +0100 Subject: [PATCH 148/380] [Mobile] - Autocomplete - Fix regression related to the Enter key code (#45997) * Mobile - Fix issue on mobile and the autocomplete feature / slash inserter where the enter key would fail to create the block and instead it creates a new Paragraph block * Add KeyCodes to the AztecView mock --- packages/react-native-aztec/src/AztecView.js | 1 + packages/rich-text/src/component/index.native.js | 1 + test/native/__mocks__/@wordpress/react-native-aztec/index.js | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react-native-aztec/src/AztecView.js b/packages/react-native-aztec/src/AztecView.js index 456fde3ed8b69..f194aa67ffe99 100644 --- a/packages/react-native-aztec/src/AztecView.js +++ b/packages/react-native-aztec/src/AztecView.js @@ -284,5 +284,6 @@ class AztecView extends Component { const RCTAztecView = requireNativeComponent( 'RCTAztecView', AztecView ); AztecView.InputState = AztecInputState; +AztecView.KeyCodes = KEYCODES; export default AztecView; diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 7fb9a25e1df63..964620580e686 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -372,6 +372,7 @@ export class RichText extends Component { this.customEditableOnKeyDown?.( { preventDefault: () => undefined, ...event, + key: RCTAztecView.KeyCodes[ event?.keyCode ], } ); this.handleDelete( event ); diff --git a/test/native/__mocks__/@wordpress/react-native-aztec/index.js b/test/native/__mocks__/@wordpress/react-native-aztec/index.js index f64e93af00295..1452aeb957c87 100644 --- a/test/native/__mocks__/@wordpress/react-native-aztec/index.js +++ b/test/native/__mocks__/@wordpress/react-native-aztec/index.js @@ -9,9 +9,11 @@ import { omit } from 'lodash'; */ import { forwardRef, useImperativeHandle, useRef } from '@wordpress/element'; -// Preserve the mock of AztecInputState to be exported with the AztecView mock. +// Preserve the mock of AztecInputState and AztecKeyCodes to be exported with the AztecView mock. const AztecInputState = jest.requireActual( '@wordpress/react-native-aztec' ) .default.InputState; +const AztecKeyCodes = jest.requireActual( '@wordpress/react-native-aztec' ) + .default.KeyCodes; const UNSUPPORTED_PROPS = [ 'style' ]; @@ -50,5 +52,6 @@ const RCTAztecView = ( { accessibilityLabel, text, ...rest }, ref ) => { const AztecView = forwardRef( RCTAztecView ); AztecView.InputState = AztecInputState; +AztecView.KeyCodes = AztecKeyCodes; export default AztecView; From 5a270b9733254ca5e5a4d26a2604ebe12e4b10f2 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:51:45 +0200 Subject: [PATCH 149/380] Lodash: Refactor editor away from _.filter() (#46059) --- .../src/components/document-outline/check.js | 8 +------- .../src/components/editor-notices/index.js | 19 ++++++------------- .../src/components/editor-snackbars/index.js | 11 +++-------- .../src/components/post-taxonomies/index.js | 10 ++-------- .../components/post-taxonomies/test/index.js | 2 +- 5 files changed, 13 insertions(+), 37 deletions(-) diff --git a/packages/editor/src/components/document-outline/check.js b/packages/editor/src/components/document-outline/check.js index 324b9380ca63a..0ae0b3435e1e4 100644 --- a/packages/editor/src/components/document-outline/check.js +++ b/packages/editor/src/components/document-outline/check.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { filter } from 'lodash'; - /** * WordPress dependencies */ @@ -10,8 +5,7 @@ import { withSelect } from '@wordpress/data'; import { store as blockEditorStore } from '@wordpress/block-editor'; function DocumentOutlineCheck( { blocks, children } ) { - const headings = filter( - blocks, + const headings = blocks.filter( ( block ) => block.name === 'core/heading' ); diff --git a/packages/editor/src/components/editor-notices/index.js b/packages/editor/src/components/editor-notices/index.js index 1dd7b8e0e31bf..ec4bd04d68920 100644 --- a/packages/editor/src/components/editor-notices/index.js +++ b/packages/editor/src/components/editor-notices/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { filter } from 'lodash'; - /** * WordPress dependencies */ @@ -17,14 +12,12 @@ import { store as noticesStore } from '@wordpress/notices'; import TemplateValidationNotice from '../template-validation-notice'; export function EditorNotices( { notices, onRemove } ) { - const dismissibleNotices = filter( notices, { - isDismissible: true, - type: 'default', - } ); - const nonDismissibleNotices = filter( notices, { - isDismissible: false, - type: 'default', - } ); + const dismissibleNotices = notices.filter( + ( { isDismissible, type } ) => isDismissible && type === 'default' + ); + const nonDismissibleNotices = notices.filter( + ( { isDismissible, type } ) => ! isDismissible && type === 'default' + ); return ( <> diff --git a/packages/editor/src/components/editor-snackbars/index.js b/packages/editor/src/components/editor-snackbars/index.js index 6dc01435eb4b4..57f0fe4bf88af 100644 --- a/packages/editor/src/components/editor-snackbars/index.js +++ b/packages/editor/src/components/editor-snackbars/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { filter } from 'lodash'; - /** * WordPress dependencies */ @@ -16,9 +11,9 @@ export default function EditorSnackbars() { [] ); const { removeNotice } = useDispatch( noticesStore ); - const snackbarNotices = filter( notices, { - type: 'snackbar', - } ); + const snackbarNotices = notices.filter( + ( { type } ) => type === 'snackbar' + ); return ( + const availableTaxonomies = ( taxonomies ?? [] ).filter( ( taxonomy ) => taxonomy.types.includes( postType ) ); - const visibleTaxonomies = filter( - availableTaxonomies, + const visibleTaxonomies = availableTaxonomies.filter( // In some circumstances .visibility can end up as undefined so optional chaining operator required. // https://github.com/WordPress/gutenberg/issues/40326 ( taxonomy ) => taxonomy.visibility?.show_ui diff --git a/packages/editor/src/components/post-taxonomies/test/index.js b/packages/editor/src/components/post-taxonomies/test/index.js index ef1a446be3d71..0a38814306175 100644 --- a/packages/editor/src/components/post-taxonomies/test/index.js +++ b/packages/editor/src/components/post-taxonomies/test/index.js @@ -85,7 +85,7 @@ describe( 'PostTaxonomies', () => { } ); it( 'should render no children if taxonomy data not available', () => { - const taxonomies = {}; + const taxonomies = null; const { container } = render( From 3835cda96c1c2688bb06a617abdd197ebe7261e6 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:52:02 +0200 Subject: [PATCH 150/380] Lodash: Refactor edit-post away from _.filter() (#46060) --- .../src/components/block-manager/index.js | 15 +++++---------- .../preferences-modal/meta-boxes-section.js | 5 ++--- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/edit-post/src/components/block-manager/index.js b/packages/edit-post/src/components/block-manager/index.js index bc141500390ad..d670ea52cf569 100644 --- a/packages/edit-post/src/components/block-manager/index.js +++ b/packages/edit-post/src/components/block-manager/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { filter } from 'lodash'; - /** * WordPress dependencies */ @@ -92,15 +87,15 @@ function BlockManager( { + blockType.category === category.slug + ) } /> ) ) } ! category ) } /> diff --git a/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js b/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js index 857eb27910bed..0ad7f518bcb70 100644 --- a/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js +++ b/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, map } from 'lodash'; +import { map } from 'lodash'; /** * WordPress dependencies @@ -23,8 +23,7 @@ export function MetaBoxesSection( { ...sectionProps } ) { // The 'Custom Fields' meta box is a special case that we handle separately. - const thirdPartyMetaBoxes = filter( - metaBoxes, + const thirdPartyMetaBoxes = metaBoxes.filter( ( { id } ) => id !== 'postcustom' ); From 15934e355cca1ce5b345036db9db4e97f70bcd78 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:52:22 +0200 Subject: [PATCH 151/380] Lodash: Refactor block editor away from _.filter() (#46064) --- packages/block-editor/src/store/selectors.js | 27 ++++++++------------ 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 0345b29ff9332..7ddb6603f2ed5 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { map, find, filter, orderBy } from 'lodash'; +import { map, find, orderBy } from 'lodash'; import createSelector from 'rememo'; /** @@ -509,24 +509,20 @@ export const getBlockParentsByBlockName = createSelector( ( state, clientId, blockName, ascending = false ) => { const parents = getBlockParents( state, clientId, ascending ); return map( - filter( - map( parents, ( id ) => ( { - id, - name: getBlockName( state, id ), - } ) ), - ( { name } ) => { - if ( Array.isArray( blockName ) ) { - return blockName.includes( name ); - } - return name === blockName; + map( parents, ( id ) => ( { + id, + name: getBlockName( state, id ), + } ) ).filter( ( { name } ) => { + if ( Array.isArray( blockName ) ) { + return blockName.includes( name ); } - ), + return name === blockName; + } ), ( { id } ) => id ); }, ( state ) => [ state.blocks.parents ] ); - /** * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. * @@ -2167,7 +2163,7 @@ export const __experimentalGetAllowedBlocks = createSelector( return; } - return filter( getBlockTypes(), ( blockType ) => + return getBlockTypes().filter( ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ); }, @@ -2292,8 +2288,7 @@ const getAllAllowedPatterns = createSelector( export const __experimentalGetAllowedPatterns = createSelector( ( state, rootClientId = null ) => { const availableParsedPatterns = getAllAllowedPatterns( state ); - const patternsAllowed = filter( - availableParsedPatterns, + const patternsAllowed = availableParsedPatterns.filter( ( { blocks } ) => blocks.every( ( { name } ) => canInsertBlockType( state, name, rootClientId ) From 21fb80e6094dc335856e47210e0f761382ab1829 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:52:46 +0200 Subject: [PATCH 152/380] Lodash: Refactor core-data away from _.filter() (#46066) --- packages/core-data/src/queried-data/reducer.js | 4 ++-- packages/core-data/src/selectors.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core-data/src/queried-data/reducer.js b/packages/core-data/src/queried-data/reducer.js index 9db1b7dad741f..e47f0220156a7 100644 --- a/packages/core-data/src/queried-data/reducer.js +++ b/packages/core-data/src/queried-data/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { map, omit, filter, mapValues } from 'lodash'; +import { map, omit, mapValues } from 'lodash'; /** * WordPress dependencies @@ -233,7 +233,7 @@ const queries = ( state = {}, action ) => { return mapValues( state, ( contextQueries ) => { return mapValues( contextQueries, ( queryItems ) => { - return filter( queryItems, ( queryId ) => { + return queryItems.filter( ( queryId ) => { return ! removedItems[ queryId ]; } ); } ); diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 4151a94824acb..93306d2226e7a 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -2,7 +2,7 @@ * External dependencies */ import createSelector from 'rememo'; -import { set, map, find, get, filter } from 'lodash'; +import { set, map, find, get } from 'lodash'; /** * WordPress dependencies @@ -183,7 +183,7 @@ export function getEntitiesByKind( state: State, kind: string ): Array< any > { * @return Array of entities with config matching kind. */ export function getEntitiesConfig( state: State, kind: string ): Array< any > { - return filter( state.entities.config, { kind } ); + return state.entities.config.filter( ( entity ) => entity.kind === kind ); } /** From 2904f8de2cfc13004842e38c1964b43b242a32b5 Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 28 Nov 2022 11:03:42 +0100 Subject: [PATCH 153/380] URLInput: keep the search results label in sync with the results list (#45806) * URLInput: reorganize the renderSuggestion condition * URLInput: simplify shouldShowInitialSuggestions which always runs with initial state * URLInput: don't updateSuggestions on change, it's done by cDU lifecycle * URLInput: cancel suggestionsRequest when updated value means no request * URLInput: set empty suggestions when updated value means no request * URLInput: move isUpdatingSuggestions to state * URLInput: put the current input value to state, in sync with search results * LinkControl: don't send currentInputValue to URLInput, it already does it internally * URLInput: mention the currentInputValue param for renderSuggestions in block-editor changelog --- packages/block-editor/CHANGELOG.md | 4 + .../src/components/link-control/README.md | 2 +- .../components/link-control/search-input.js | 1 - .../src/components/url-input/index.js | 130 ++++++++---------- 4 files changed, 62 insertions(+), 75 deletions(-) diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 4d37bf5737599..0ce86cc9cb110 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancement + +- `URLInput`: the `renderSuggestions` callback prop now receives `currentInputValue` as a new parameter ([45806](https://github.com/WordPress/gutenberg/pull/45806)). + ## 10.5.0 (2022-11-16) ### Enhancement diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md index 3d3dd7b0aaa21..c3fc7262adb95 100644 --- a/packages/block-editor/src/components/link-control/README.md +++ b/packages/block-editor/src/components/link-control/README.md @@ -322,10 +322,10 @@ The following properties are provided by URLInput: - suggestions - selectedSuggestion - suggestionsListProps +- currentInputValue The following extra properties are provided by LinkControlSearchInput: -- currentInputValue - createSuggestionButtonText - handleSuggestionClick - instanceId diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index cabe52f907469..95c925a45d830 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -81,7 +81,6 @@ const LinkControlSearchInput = forwardRef( ...props, instanceId, withCreateSuggestion, - currentInputValue: value, createSuggestionButtonText, suggestionsQuery, handleSuggestionClick: ( suggestion ) => { diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 3858f3983103a..0eb97730e15d1 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -60,13 +60,14 @@ class URLInput extends Component { this.suggestionNodes = []; - this.isUpdatingSuggestions = false; + this.suggestionsRequest = null; this.state = { suggestions: [], showSuggestions: false, + isUpdatingSuggestions: false, + suggestionsValue: null, selectedSuggestion: null, - suggestionsListboxId: '', suggestionOptionIdPrefix: '', }; @@ -103,7 +104,7 @@ class URLInput extends Component { if ( prevProps.value !== value && ! this.props.disableSuggestions && - ! this.isUpdatingSuggestions + ! this.state.isUpdatingSuggestions ) { if ( value?.length ) { // If the new value is not empty we need to update with suggestions for it. @@ -123,7 +124,7 @@ class URLInput extends Component { componentWillUnmount() { this.suggestionsRequest?.cancel?.(); - delete this.suggestionsRequest; + this.suggestionsRequest = null; } bindSuggestionNode( index ) { @@ -133,14 +134,10 @@ class URLInput extends Component { } shouldShowInitialSuggestions() { - const { suggestions } = this.state; const { __experimentalShowInitialSuggestions = false, value } = this.props; return ( - ! this.isUpdatingSuggestions && - __experimentalShowInitialSuggestions && - ! ( value && value.length ) && - ! ( suggestions && suggestions.length ) + __experimentalShowInitialSuggestions && ! ( value && value.length ) ); } @@ -170,8 +167,13 @@ class URLInput extends Component { ! isInitialSuggestions && ( value.length < 2 || ( ! handleURLSuggestions && isURL( value ) ) ) ) { + this.suggestionsRequest?.cancel?.(); + this.suggestionsRequest = null; + this.setState( { + suggestions: [], showSuggestions: false, + suggestionsValue: value, selectedSuggestion: null, loading: false, } ); @@ -179,9 +181,8 @@ class URLInput extends Component { return; } - this.isUpdatingSuggestions = true; - this.setState( { + isUpdatingSuggestions: true, selectedSuggestion: null, loading: true, } ); @@ -201,6 +202,8 @@ class URLInput extends Component { this.setState( { suggestions, + isUpdatingSuggestions: false, + suggestionsValue: value, loading: false, showSuggestions: !! suggestions.length, } ); @@ -224,15 +227,16 @@ class URLInput extends Component { 'assertive' ); } - this.isUpdatingSuggestions = false; } ) .catch( () => { - if ( this.suggestionsRequest === request ) { - this.setState( { - loading: false, - } ); - this.isUpdatingSuggestions = false; + if ( this.suggestionsRequest !== request ) { + return; } + + this.setState( { + isUpdatingSuggestions: false, + loading: false, + } ); } ); // Note that this assignment is handled *before* the async search request @@ -241,12 +245,7 @@ class URLInput extends Component { } onChange( event ) { - const inputValue = event.target.value; - - this.props.onChange( inputValue ); - if ( ! this.props.disableSuggestions ) { - this.updateSuggestions( inputValue ); - } + this.props.onChange( event.target.value ); } onFocus() { @@ -258,7 +257,7 @@ class URLInput extends Component { if ( value && ! disableSuggestions && - ! this.isUpdatingSuggestions && + ! this.state.isUpdatingSuggestions && ! ( suggestions && suggestions.length ) ) { // Ensure the suggestions are updated with the current input value. @@ -490,19 +489,22 @@ class URLInput extends Component { const { className, __experimentalRenderSuggestions: renderSuggestions, - value = '', - __experimentalShowInitialSuggestions = false, } = this.props; const { showSuggestions, suggestions, + suggestionsValue, selectedSuggestion, suggestionsListboxId, suggestionOptionIdPrefix, loading, } = this.state; + if ( ! showSuggestions || suggestions.length === 0 ) { + return null; + } + const suggestionsListProps = { id: suggestionsListboxId, ref: this.autocompleteRef, @@ -519,11 +521,7 @@ class URLInput extends Component { }; }; - if ( - isFunction( renderSuggestions ) && - showSuggestions && - !! suggestions.length - ) { + if ( isFunction( renderSuggestions ) ) { return renderSuggestions( { suggestions, selectedSuggestion, @@ -531,52 +529,38 @@ class URLInput extends Component { buildSuggestionItemProps, isLoading: loading, handleSuggestionClick: this.handleOnClick, - isInitialSuggestions: - __experimentalShowInitialSuggestions && - ! ( value && value.length ), + isInitialSuggestions: ! suggestionsValue?.length, + currentInputValue: suggestionsValue, } ); } - if ( - ! isFunction( renderSuggestions ) && - showSuggestions && - !! suggestions.length - ) { - return ( - -
      - { suggestions.map( ( suggestion, index ) => ( - - ) ) } -
      -
      - ); - } - return null; + ) } + onClick={ () => this.handleOnClick( suggestion ) } + > + { suggestion.title } + + ) ) } +
      + + ); } } From ec2bdc59c4c4cdf8d86cdd432ee30eaca6f14eed Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:03:06 +0200 Subject: [PATCH 154/380] Lodash: Refactor blocks away from _.filter() (#46062) --- packages/blocks/src/store/reducer.js | 16 +++-- packages/blocks/src/store/selectors.js | 4 +- packages/blocks/src/store/test/selectors.js | 68 +++++++++++---------- 3 files changed, 48 insertions(+), 40 deletions(-) diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 6770dd6b67f26..29bd7c9ef02aa 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { filter, find, get, isEmpty, map, mapValues } from 'lodash'; +import { find, get, isEmpty, map, mapValues } from 'lodash'; /** * WordPress dependencies @@ -144,8 +144,11 @@ export function blockStyles( state = {}, action ) { case 'REMOVE_BLOCK_STYLES': return { ...state, - [ action.blockName ]: filter( - get( state, [ action.blockName ], [] ), + [ action.blockName ]: get( + state, + [ action.blockName ], + [] + ).filter( ( style ) => action.styleNames.indexOf( style.name ) === -1 ), }; @@ -195,8 +198,11 @@ export function blockVariations( state = {}, action ) { case 'REMOVE_BLOCK_VARIATIONS': return { ...state, - [ action.blockName ]: filter( - get( state, [ action.blockName ], [] ), + [ action.blockName ]: get( + state, + [ action.blockName ], + [] + ).filter( ( variation ) => action.variationNames.indexOf( variation.name ) === -1 ), diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 298d7fc12141a..27cacef408582 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -3,7 +3,7 @@ */ import createSelector from 'rememo'; import removeAccents from 'remove-accents'; -import { filter, get, map } from 'lodash'; +import { get, map } from 'lodash'; /** * WordPress dependencies @@ -554,7 +554,7 @@ export function getGroupingBlockName( state ) { export const getChildBlockNames = createSelector( ( state, blockName ) => { return map( - filter( state.blockTypes, ( blockType ) => { + getBlockTypes( state ).filter( ( blockType ) => { return blockType.parent?.includes( blockName ); } ), ( { name } ) => name diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 8793e1e354e44..1fda11d72311a 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -86,26 +86,28 @@ describe( 'selectors', () => { describe( 'getChildBlockNames', () => { it( 'should return an empty array if state is empty', () => { - const state = {}; + const state = { + blockTypes: {}, + }; expect( getChildBlockNames( state, 'parent1' ) ).toHaveLength( 0 ); } ); it( 'should return an empty array if no children exist', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent2' ], }, - { + parent3: { name: 'parent3', }, - ], + }, }; expect( getChildBlockNames( state, 'parent3' ) ).toHaveLength( 0 ); @@ -113,15 +115,15 @@ describe( 'selectors', () => { it( 'should return an empty array if the parent block is not found', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + parent1: { name: 'parent1', }, - ], + }, }; expect( getChildBlockNames( state, 'parent3' ) ).toHaveLength( 0 ); @@ -129,29 +131,29 @@ describe( 'selectors', () => { it( 'should return an array with the child block names', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent2' ], }, - { + child3: { name: 'child3', parent: [ 'parent1' ], }, - { + child4: { name: 'child4', }, - { + parent1: { name: 'parent1', }, - { + parent2: { name: 'parent2', }, - ], + }, }; expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ @@ -162,25 +164,25 @@ describe( 'selectors', () => { it( 'should return an array with the child block names even if only one child exists', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent2' ], }, - { + child4: { name: 'child4', }, - { + parent1: { name: 'parent1', }, - { + parent2: { name: 'parent2', }, - ], + }, }; expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ @@ -190,29 +192,29 @@ describe( 'selectors', () => { it( 'should return an array with the child block names even if children have multiple parents', () => { const state = { - blockTypes: [ - { + blockTypes: { + child1: { name: 'child1', parent: [ 'parent1' ], }, - { + child2: { name: 'child2', parent: [ 'parent1', 'parent2' ], }, - { + child3: { name: 'child3', parent: [ 'parent1' ], }, - { + child4: { name: 'child4', }, - { + parent1: { name: 'parent1', }, - { + parent2: { name: 'parent2', }, - ], + }, }; expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ From 606aee8a981f9fd402297bbfa1cf72c84bcd8bde Mon Sep 17 00:00:00 2001 From: Jarda Snajdr Date: Mon, 28 Nov 2022 14:35:46 +0100 Subject: [PATCH 155/380] useBlockEditorSettings: return const empty array to avoid rerenders (#46117) --- .../src/components/provider/use-block-editor-settings.js | 4 +++- .../components/provider/use-block-editor-settings.native.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 69b4f9b42cfc6..3f4ed5a15fe37 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -17,6 +17,8 @@ import { __ } from '@wordpress/i18n'; import { mediaUpload } from '../../utils'; import { store as editorStore } from '../../store'; +const EMPTY_BLOCKS_LIST = []; + /** * React hook used to compute the block editor settings to use for the post editor. * @@ -51,7 +53,7 @@ function useBlockEditorSettings( settings, hasTemplate ) { 'wp_block', { per_page: -1 } ) - : [], // Reusable blocks are fetched in the native version of this hook. + : EMPTY_BLOCKS_LIST, // Reusable blocks are fetched in the native version of this hook. hasUploadPermissions: canUser( 'create', 'media' ) ?? true, userCanCreatePages: canUser( 'create', 'pages' ), pageOnFront: siteSettings?.page_on_front, diff --git a/packages/editor/src/components/provider/use-block-editor-settings.native.js b/packages/editor/src/components/provider/use-block-editor-settings.native.js index 53d703ee762fa..96ff12d2d76db 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.native.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.native.js @@ -11,6 +11,8 @@ import { store as coreStore } from '@wordpress/core-data'; import useBlockEditorSettings from './use-block-editor-settings.js'; import { store as editorStore } from '../../store'; +const EMPTY_BLOCKS_LIST = []; + function useNativeBlockEditorSettings( settings, hasTemplate ) { const capabilities = settings.capabilities ?? {}; const editorSettings = useBlockEditorSettings( settings, hasTemplate ); @@ -27,7 +29,7 @@ function useNativeBlockEditorSettings( settings, hasTemplate ) { // Related issue: https://github.com/wordpress-mobile/gutenberg-mobile/issues/2661 { per_page: 100 } ) - : [], + : EMPTY_BLOCKS_LIST, isTitleSelected: select( editorStore ).isPostTitleSelected(), }; }, From ca377b5432cc8cad155eaa3e267fa07fb24fbb48 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 28 Nov 2022 22:55:59 +0900 Subject: [PATCH 156/380] LinkedButton: remove unnecessary span tag (#46063) * LinkedButton: remove unnecessary span tag * Update changelog --- packages/components/CHANGELOG.md | 1 + .../src/box-control/linked-button.js | 19 ++++++++----------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 22a08c2f0dd08..d2580e0b009b5 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -12,6 +12,7 @@ ### Internal +- `LinkedButton`: remove unnecessary `span` tag ([#46063](https://github.com/WordPress/gutenberg/pull/46063)) - NumberControl: refactor styles/tests/stories to TypeScript, replace fireEvent with user-event ([#45990](https://github.com/WordPress/gutenberg/pull/45990)). ## 22.1.0 (2022-11-16) diff --git a/packages/components/src/box-control/linked-button.js b/packages/components/src/box-control/linked-button.js index 39de0e894783f..67fb1cebf7b09 100644 --- a/packages/components/src/box-control/linked-button.js +++ b/packages/components/src/box-control/linked-button.js @@ -13,19 +13,16 @@ import Tooltip from '../tooltip'; export default function LinkedButton( { isLinked, ...props } ) { const label = isLinked ? __( 'Unlink sides' ) : __( 'Link sides' ); - // TODO: Remove span after merging https://github.com/WordPress/gutenberg/pull/44198 return ( - - + + ); + + const button = screen.getByRole( 'button', { name: 'Hover Me!' } ); + await user.hover( button ); + + expect( await screen.findByText( 'shortcut text' ) ).toBeVisible(); + } ); + + it( 'should render the shortcut display text and aria-label when an object is passed as the shortcut with the correct properties', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( + + + + ); + + const button = screen.getByRole( 'button', { name: 'Hover Me!' } ); + await user.hover( button ); + + expect( + await screen.findByLabelText( 'shortcut label' ) + ).toHaveTextContent( 'shortcut text' ); + } ); } ); } ); From 4d940e4e32ab56b5096f0a5387417c62e6a6a05d Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 29 Nov 2022 15:38:00 +0400 Subject: [PATCH 167/380] Gallery: Use unbound query when fetching image details (#46143) --- packages/block-library/src/gallery/use-get-media.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/gallery/use-get-media.js b/packages/block-library/src/gallery/use-get-media.js index 36559447ae291..a6dbe9faabf0b 100644 --- a/packages/block-library/src/gallery/use-get-media.js +++ b/packages/block-library/src/gallery/use-get-media.js @@ -28,7 +28,7 @@ export default function useGetMedia( innerBlockImages ) { return ( select( coreStore ).getMediaItems( { include: imageIds.join( ',' ), - per_page: imageIds.length, + per_page: -1, orderby: 'include', } ) ?? EMPTY_IMAGE_MEDIA ); From 8cf5051bc76000710cfd043e305e1a3f010c2691 Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:22:04 +0200 Subject: [PATCH 168/380] Components: Fix no-node-access violations in Card tests (#46158) --- packages/components/src/card/test/index.tsx | 52 +++++++++++++-------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/components/src/card/test/index.tsx b/packages/components/src/card/test/index.tsx index ee10655fae0b2..fb1c6d972aa69 100644 --- a/packages/components/src/card/test/index.tsx +++ b/packages/components/src/card/test/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; /** * Internal dependencies @@ -38,27 +38,39 @@ describe( 'Card', () => { } ); it( 'should remove borders when the isBorderless prop is true', () => { - const { rerender, container } = render( - Code is Poetry + const { rerender } = render( + Code is Poetry ); - expect( container.firstChild ).not.toHaveStyle( + + expect( screen.getByTestId( 'card-wrapper' ) ).not.toHaveStyle( 'box-shadow: none' ); - rerender( Code is Poetry ); - expect( container.firstChild ).toHaveStyle( 'box-shadow: none' ); + rerender( + + Code is Poetry + + ); + + expect( screen.getByTestId( 'card-wrapper' ) ).toHaveStyle( + 'box-shadow: none' + ); } ); it( 'should add rounded border when the isRounded prop is true', () => { - const { container: containerRounded } = render( - Code is Poetry + render( + + Code is Poetry + ); - const { container: containerSquared } = render( - Code is Poetry + render( + + Code is Poetry + ); expect( - containerRounded.firstElementChild - ).toMatchStyleDiffSnapshot( containerSquared.firstElementChild ); + screen.getByTestId( 'card-rounded' ) + ).toMatchStyleDiffSnapshot( screen.getByTestId( 'card-squared' ) ); } ); it( 'should show a box shadow when the elevation prop is greater than 0', () => { @@ -94,7 +106,7 @@ describe( 'Card', () => { it( 'should warn when the isElevated prop is passed', () => { // `isElevated` is automatically converted to `elevation="2"` const { container } = render( - Code is Poetry + Code is Poetry ); expect( container ).toMatchSnapshot(); @@ -103,7 +115,7 @@ describe( 'Card', () => { it( 'should pass the isBorderless and isSize props from its context to its sub-components', () => { const { container: withoutBorderLarge } = render( - + Header Body Footer @@ -121,14 +133,14 @@ describe( 'Card', () => { it( 'should get the isBorderless and isSize props (passed from its context) overriddenwhen the same props is specified directly on the component', () => { const { container: withoutBorder } = render( - + Header Body Footer ); const { container: withBorder } = render( - + Header @@ -165,7 +177,7 @@ describe( 'Card', () => { it( 'should render with a darker background color when isShady is true', () => { const { container } = render( Header ); const { container: containerShady } = render( - Header + Header ); expect( container ).toMatchDiffSnapshot( containerShady ); } ); @@ -175,7 +187,7 @@ describe( 'Card', () => { it( 'should render with a darker background color when isShady is true', () => { const { container } = render( Footer ); const { container: containerShady } = render( - Footer + Footer ); expect( container ).toMatchDiffSnapshot( containerShady ); } ); @@ -193,14 +205,14 @@ describe( 'Card', () => { it( 'should render with a darker background color when isShady is true', () => { const { container } = render( Body ); const { container: containerShady } = render( - Body + Body ); expect( container ).toMatchDiffSnapshot( containerShady ); } ); it( 'should allow scrolling content with the scrollable prop is true', () => { const { container: containerScrollable } = render( - Body + Body ); const { container } = render( Body ); expect( container ).toMatchDiffSnapshot( containerScrollable ); From 59ed345d8373fb25d10a4fbb510f63f0e02305cf Mon Sep 17 00:00:00 2001 From: Marin Atanasov <8436925+tyxla@users.noreply.github.com> Date: Tue, 29 Nov 2022 15:31:32 +0200 Subject: [PATCH 169/380] Components: Fix no-node-access violations in Disabled tests (#46156) --- .../components/src/disabled/test/index.tsx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/components/src/disabled/test/index.tsx b/packages/components/src/disabled/test/index.tsx index cedcdad872701..fddbd1159637b 100644 --- a/packages/components/src/disabled/test/index.tsx +++ b/packages/components/src/disabled/test/index.tsx @@ -18,48 +18,58 @@ describe( 'Disabled', () => { ); it( 'will disable all fields', () => { - const { container } = render( - + render( +
      ); - expect( container.firstChild ).toHaveAttribute( 'inert' ); + expect( screen.getByTestId( 'disabled-wrapper' ) ).toHaveAttribute( + 'inert' + ); } ); it( 'should cleanly un-disable via reconciliation', () => { const MaybeDisable = ( { isDisabled = true } ) => isDisabled ? ( - + ) : ( ); - const { container, rerender } = render( ); + const { rerender } = render( ); - expect( container.firstChild ).toHaveAttribute( 'inert' ); + expect( screen.getByTestId( 'disabled-wrapper' ) ).toHaveAttribute( + 'inert' + ); rerender( ); - expect( container.firstChild ).not.toHaveAttribute( 'inert' ); + expect( + screen.queryByTestId( 'disabled-wrapper' ) + ).not.toBeInTheDocument(); } ); it( 'will disable or enable descendant fields based on the isDisabled prop value', () => { const MaybeDisable = ( { isDisabled = true } ) => ( - + ); - const { rerender, container } = render( ); + const { rerender } = render( ); - expect( container.firstChild ).toHaveAttribute( 'inert' ); + expect( screen.getByTestId( 'disabled-wrapper' ) ).toHaveAttribute( + 'inert' + ); rerender( ); - expect( container.firstChild ).not.toHaveAttribute( 'inert' ); + expect( screen.getByTestId( 'disabled-wrapper' ) ).not.toHaveAttribute( + 'inert' + ); } ); it( 'should preserve input values when toggling the isDisabled prop', async () => { From 2b8a42dd5facce243f8670ba6af7870f16eee722 Mon Sep 17 00:00:00 2001 From: Devanshi Joshi <86941664+devanshijoshi9@users.noreply.github.com> Date: Tue, 29 Nov 2022 19:22:19 +0530 Subject: [PATCH 170/380] Components: ToggleControl text overflows when it has a long label (#45962) * fix: Add style to resolve togglecontrol lable text overflow * Feat: Remove style.scss file from togglecontrol component * Feat: Use FlexBlock component instead of new stylesheet * Feat: Add changelog for ToggleControl component * Feat: Run test:unit:update script to update Snapshot --- packages/components/CHANGELOG.md | 1 + .../components/src/toggle-control/index.tsx | 6 +- .../enable-custom-fields.js.snap | 60 +++++++++++++++++-- .../test/__snapshots__/index.js.snap | 53 +++++++++++----- .../__snapshots__/meta-boxes-section.js.snap | 57 ++++++++++++++++-- 5 files changed, 151 insertions(+), 26 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 025a767c061a9..51fd7a415b79f 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -46,6 +46,7 @@ - `Icon`: Making size prop work for icon components using dash icon strings ([#45593](https://github.com/WordPress/gutenberg/pull/45593)) - `ToolsPanelItem`: Prevent unintended calls to onDeselect when parent panel is remounted and item is rendered via SlotFill ([#45673](https://github.com/WordPress/gutenberg/pull/45673)) - `ColorPicker`: Prevent all number fields from becoming "0" when one of them is an empty string ([#45649](https://github.com/WordPress/gutenberg/pull/45649)). +- `ToggleControl`: Fix toggle control label text overflow ([#45962](https://github.com/WordPress/gutenberg/pull/45962)). ### Internal diff --git a/packages/components/src/toggle-control/index.tsx b/packages/components/src/toggle-control/index.tsx index 4dfcda30e7cab..f71c807b9d4fc 100644 --- a/packages/components/src/toggle-control/index.tsx +++ b/packages/components/src/toggle-control/index.tsx @@ -12,6 +12,7 @@ import { useInstanceId } from '@wordpress/compose'; /** * Internal dependencies */ +import { FlexBlock } from '../flex'; import FormToggle from '../form-toggle'; import BaseControl from '../base-control'; import type { WordPressComponentProps } from '../ui/context/wordpress-component'; @@ -94,12 +95,13 @@ export function ToggleControl( { aria-describedby={ describedBy } disabled={ disabled } /> - + ); diff --git a/packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap b/packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap index 2485d91e9fdb9..a405a1c39ee1c 100644 --- a/packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap +++ b/packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap @@ -41,6 +41,17 @@ exports[`EnableCustomFieldsOption renders a checked checkbox and a confirmation min-width: 0; } +.emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +
      @@ -134,6 +147,17 @@ exports[`EnableCustomFieldsOption renders a checked checkbox when custom fields min-width: 0; } +.emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +
      @@ -217,6 +243,17 @@ exports[`EnableCustomFieldsOption renders an unchecked checkbox and a confirmati min-width: 0; } +.emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +
      @@ -311,6 +350,17 @@ exports[`EnableCustomFieldsOption renders an unchecked checkbox when custom fiel min-width: 0; } +.emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +
      diff --git a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap index f9f7be89c82a0..627eb2d284ddf 100644 --- a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/index.js.snap @@ -42,6 +42,17 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active } .emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.emotion-8 { margin-top: calc(4px * 2); margin-bottom: 0; font-size: 12px; @@ -186,7 +197,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

      Review settings, such as visibility and tags. @@ -250,7 +263,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

      Reduce visual distractions by hiding the toolbar and other elements to focus on writing. @@ -296,7 +311,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

      Highlights the current block and fades other content. @@ -342,7 +359,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

      Show text instead of icons on buttons. @@ -388,7 +407,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

      Opens the block list view sidebar by default. @@ -434,7 +455,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

      Make the editor look like your theme. @@ -480,7 +503,9 @@ exports[`EditPostPreferencesModal should match snapshot when the modal is active />

      Shows block breadcrumbs at the bottom of the editor. diff --git a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/meta-boxes-section.js.snap b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/meta-boxes-section.js.snap index 087120098c583..2246e0f12fdef 100644 --- a/packages/edit-post/src/components/preferences-modal/test/__snapshots__/meta-boxes-section.js.snap +++ b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/meta-boxes-section.js.snap @@ -41,6 +41,17 @@ exports[`MetaBoxesSection renders a Custom Fields option 1`] = ` min-width: 0; } +.emotion-6 { + display: block; + max-height: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +

      @@ -83,7 +94,9 @@ exports[`MetaBoxesSection renders a Custom Fields option 1`] = ` />