diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a54091e4745989..f35a0123f9c987 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -125,7 +125,7 @@ /packages/block-editor/src/components/rich-text @ellatrix @fluiddot @dcalhoun # Project Management -/.github +/.github @desrosj /packages/project-management-automation /packages/report-flaky-tests @kevin940726 diff --git a/.github/workflows/php-changes-detection.yml b/.github/workflows/php-changes-detection.yml index 6a13d4d014fc69..47701d35025d83 100644 --- a/.github/workflows/php-changes-detection.yml +++ b/.github/workflows/php-changes-detection.yml @@ -17,7 +17,7 @@ jobs: - name: Get changed PHP files id: changed-files-php - uses: tj-actions/changed-files@90a06d6ba9543371ab4df8eeca0be07ca6054959 # v42.0.2 + uses: tj-actions/changed-files@3f54ebb830831fc121d3263c1857cfbdc310cdb9 # v42.0.4 with: files: | *.{php} diff --git a/docs/contributors/code/react-native/README.md b/docs/contributors/code/react-native/README.md index 10df7268ddddb6..dd6afc2e8bd561 100644 --- a/docs/contributors/code/react-native/README.md +++ b/docs/contributors/code/react-native/README.md @@ -1,6 +1,6 @@ # React Native mobile editor -The Gutenberg repository includes the source for the [React Native](https://facebook.github.io/react-native/) based editor for mobile. +The Gutenberg repository includes the source for the [React Native](https://reactnative.dev/) based editor for mobile. ## Mind the mobile diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 189fe6d2f01a06..0b800757b4ecd0 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -72,7 +72,7 @@ Settings related to shadows. | Property | Type | Default | Props | | --- | --- | --- |--- | -| defaultPresets | boolean | true | | +| defaultPresets | boolean | false | | | presets | array | | name, shadow, slug | --- diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 82e5503a6b225d..0d03a89bd1d3c6 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -670,6 +670,7 @@ public static function get_element_class_name( $element ) { array( 'spacing', 'margin' ), array( 'spacing', 'padding' ), array( 'typography', 'lineHeight' ), + array( 'shadow', 'defaultPresets' ), ); /** diff --git a/lib/theme.json b/lib/theme.json index c2ed7fdca39ed5..5f261e2c120043 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -191,7 +191,7 @@ "text": true }, "shadow": { - "defaultPresets": true, + "defaultPresets": false, "presets": [ { "name": "Natural", diff --git a/packages/block-editor/src/components/block-edit/context.js b/packages/block-editor/src/components/block-edit/context.js index b280cc9c51f6b8..e8480912a87f03 100644 --- a/packages/block-editor/src/components/block-edit/context.js +++ b/packages/block-editor/src/components/block-edit/context.js @@ -6,6 +6,7 @@ import { createContext, useContext } from '@wordpress/element'; export const mayDisplayControlsKey = Symbol( 'mayDisplayControls' ); export const mayDisplayParentControlsKey = Symbol( 'mayDisplayParentControls' ); export const blockEditingModeKey = Symbol( 'blockEditingMode' ); +export const blockBindingsKey = Symbol( 'blockBindings' ); export const DEFAULT_BLOCK_EDIT_CONTEXT = { name: '', diff --git a/packages/block-editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js index 457cd919f89381..4e94a8a427510d 100644 --- a/packages/block-editor/src/components/block-edit/index.js +++ b/packages/block-editor/src/components/block-edit/index.js @@ -14,6 +14,7 @@ import { mayDisplayControlsKey, mayDisplayParentControlsKey, blockEditingModeKey, + blockBindingsKey, } from './context'; /** @@ -41,7 +42,8 @@ export default function BlockEdit( { attributes = {}, __unstableLayoutClassNames, } = props; - const { layout = null } = attributes; + const { layout = null, metadata = {} } = attributes; + const { bindings } = metadata; const layoutSupport = hasBlockSupport( name, 'layout', false ) || hasBlockSupport( name, '__experimentalLayout', false ); @@ -62,6 +64,7 @@ export default function BlockEdit( { [ mayDisplayControlsKey ]: mayDisplayControls, [ mayDisplayParentControlsKey ]: mayDisplayParentControls, [ blockEditingModeKey ]: blockEditingMode, + [ blockBindingsKey ]: bindings, } ), [ name, @@ -73,6 +76,7 @@ export default function BlockEdit( { mayDisplayControls, mayDisplayParentControls, blockEditingMode, + bindings, ] ) } > diff --git a/packages/block-editor/src/components/block-list/content.scss b/packages/block-editor/src/components/block-list/content.scss index f12e63c6d7663e..de9c70d4f91f7d 100644 --- a/packages/block-editor/src/components/block-list/content.scss +++ b/packages/block-editor/src/components/block-list/content.scss @@ -453,7 +453,8 @@ _::-webkit-full-page-media, _:future, :root .has-multi-selection .block-editor-b } } -.block-editor-iframe__body { +.block-editor-iframe__html { transition: all 0.3s; + background-color: $gray-300; transform-origin: top center; } diff --git a/packages/block-editor/src/components/global-styles/border-panel.js b/packages/block-editor/src/components/global-styles/border-panel.js index f8144f1545aebe..5829d90cdf3359 100644 --- a/packages/block-editor/src/components/global-styles/border-panel.js +++ b/packages/block-editor/src/components/global-styles/border-panel.js @@ -22,11 +22,7 @@ import { getValueFromVariable, TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils'; import { overrideOrigins } from '../../store/get-block-settings'; import { setImmutably } from '../../utils/object'; import { getBorderPanelLabel } from '../../hooks/border'; -import { ShadowPopover } from './shadow-panel-components'; - -function useHasShadowControl( settings ) { - return !! settings?.shadow; -} +import { ShadowPopover, useShadowPresets } from './shadow-panel-components'; export function useHasBorderPanel( settings ) { const controls = [ @@ -56,6 +52,11 @@ function useHasBorderWidthControl( settings ) { return settings?.border?.width; } +function useHasShadowControl( settings ) { + const shadows = useShadowPresets( settings ); + return !! settings?.shadow && shadows.length > 0; +} + function BorderToolsPanel( { resetAllFilter, onChange, diff --git a/packages/block-editor/src/components/global-styles/shadow-panel-components.js b/packages/block-editor/src/components/global-styles/shadow-panel-components.js index 0dc2367e7eda2f..0a8028488993f1 100644 --- a/packages/block-editor/src/components/global-styles/shadow-panel-components.js +++ b/packages/block-editor/src/components/global-styles/shadow-panel-components.js @@ -12,6 +12,7 @@ import { Dropdown, privateApis as componentsPrivateApis, } from '@wordpress/components'; +import { useMemo } from '@wordpress/element'; import { shadow as shadowIcon, Icon, check } from '@wordpress/icons'; /** @@ -24,15 +25,16 @@ import classNames from 'classnames'; */ import { unlock } from '../../lock-unlock'; -export function ShadowPopoverContainer( { shadow, onShadowChange, settings } ) { - const defaultShadows = settings?.shadow?.presets?.default || []; - const themeShadows = settings?.shadow?.presets?.theme || []; - const defaultPresetsEnabled = settings?.shadow?.defaultPresets; +/** + * Shared reference to an empty array for cases where it is important to avoid + * returning a new array reference on every invocation. + * + * @type {Array} + */ +const EMPTY_ARRAY = []; - const shadows = [ - ...( defaultPresetsEnabled ? defaultShadows : [] ), - ...themeShadows, - ]; +export function ShadowPopoverContainer( { shadow, onShadowChange, settings } ) { + const shadows = useShadowPresets( settings ); return (
@@ -43,6 +45,14 @@ export function ShadowPopoverContainer( { shadow, onShadowChange, settings } ) { activeShadow={ shadow } onSelect={ onShadowChange } /> +
+ +
); @@ -64,6 +74,7 @@ export function ShadowPresets( { presets, activeShadow, onSelect } ) { key={ slug } label={ name } isActive={ shadow === activeShadow } + type={ slug === 'unset' ? 'unset' : 'preset' } onSelect={ () => onSelect( shadow === activeShadow ? undefined : shadow ) } @@ -74,7 +85,7 @@ export function ShadowPresets( { presets, activeShadow, onSelect } ) { ); } -export function ShadowIndicator( { label, isActive, onSelect, shadow } ) { +export function ShadowIndicator( { type, label, isActive, onSelect, shadow } ) { const { CompositeItemV2: CompositeItem } = unlock( componentsPrivateApis ); return ( { + if ( ! settings?.shadow ) { + return EMPTY_ARRAY; + } + + const defaultPresetsEnabled = settings?.shadow?.defaultPresets; + const { default: defaultShadows, theme: themeShadows } = + settings?.shadow?.presets ?? {}; + const unsetShadow = { + name: __( 'Unset' ), + slug: 'unset', + shadow: 'none', + }; + + const shadowPresets = [ + ...( ( defaultPresetsEnabled && defaultShadows ) || EMPTY_ARRAY ), + ...( themeShadows || EMPTY_ARRAY ), + ]; + if ( shadowPresets.length ) { + shadowPresets.unshift( unsetShadow ); + } + + return shadowPresets; + }, [ settings ] ); +} diff --git a/packages/block-editor/src/components/global-styles/style.scss b/packages/block-editor/src/components/global-styles/style.scss index a47af6cd35e996..d357d2e65ab72a 100644 --- a/packages/block-editor/src/components/global-styles/style.scss +++ b/packages/block-editor/src/components/global-styles/style.scss @@ -16,6 +16,10 @@ padding-bottom: $grid-unit-10; } +.block-editor-global-styles__clear-shadow { + text-align: right; +} + .block-editor-global-styles-filters-panel__dropdown, .block-editor-global-styles__shadow-dropdown { display: block; @@ -54,6 +58,10 @@ &:hover { transform: scale(1.2); } + + &.unset { + background: linear-gradient(-45deg, transparent 48%, $gray-300 48%, $gray-300 52%, transparent 52%); + } } .block-editor-global-styles-advanced-panel__custom-css-input textarea { diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js index 673562fde34f58..13ce1116336c95 100644 --- a/packages/block-editor/src/components/iframe/index.js +++ b/packages/block-editor/src/components/iframe/index.js @@ -30,7 +30,7 @@ import { useBlockSelectionClearer } from '../block-selection-clearer'; import { useWritingFlow } from '../writing-flow'; import { getCompatibilityStyles } from './get-compatibility-styles'; import { store as blockEditorStore } from '../../store'; - +import calculateScale from '../../utils/calculate-scale'; function bubbleEvent( event, Constructor, frame ) { const init = {}; @@ -104,27 +104,52 @@ function Iframe( { contentRef, children, tabIndex = 0, - scale = 1, - frameSize = 0, - expand = false, + shouldZoom = false, readonly, forwardedRef: ref, ...props } ) { - const { resolvedAssets, isPreviewMode } = useSelect( ( select ) => { - const settings = select( blockEditorStore ).getSettings(); - return { - resolvedAssets: settings.__unstableResolvedAssets, - isPreviewMode: settings.__unstableIsPreviewMode, - }; - }, [] ); + const { resolvedAssets, isPreviewMode, isZoomOutMode } = useSelect( + ( select ) => { + const { getSettings, __unstableGetEditorMode } = + select( blockEditorStore ); + const settings = getSettings(); + return { + resolvedAssets: settings.__unstableResolvedAssets, + isPreviewMode: settings.__unstableIsPreviewMode, + isZoomOutMode: __unstableGetEditorMode() === 'zoom-out', + }; + }, + [] + ); const { styles = '', scripts = '' } = resolvedAssets; const [ iframeDocument, setIframeDocument ] = useState(); const [ bodyClasses, setBodyClasses ] = useState( [] ); const clearerRef = useBlockSelectionClearer(); const [ before, writingFlowRef, after ] = useWritingFlow(); - const [ contentResizeListener, { height: contentHeight } ] = - useResizeObserver(); + const [ + contentResizeListener, + { height: contentHeight, width: contentWidth }, + ] = useResizeObserver(); + + // When zoom-out mode is enabled, the iframe is scaled down to fit the + // content within the viewport. + // At 1000px wide, the iframe is scaled to 45%. + // At 400px wide, the iframe is scaled to 90%. + const scale = + isZoomOutMode && shouldZoom + ? calculateScale( + { + maxWidth: 1000, + minWidth: 400, + maxScale: 0.45, + minScale: 0.9, + }, + contentWidth + ) + : 1; + const frameSize = isZoomOutMode ? 100 : 0; + const setRef = useRefEffect( ( node ) => { node._load = () => { setIframeDocument( node.contentDocument ); @@ -139,6 +164,8 @@ function Iframe( { const { documentElement } = contentDocument; iFrameDocument = contentDocument; + documentElement.classList.add( 'block-editor-iframe__html' ); + clearerRef( documentElement ); // Ideally ALL classes that are added through get_body_class should @@ -241,6 +268,21 @@ function Iframe( { // top or bottom margin is 0.55 / 2 ((1 - scale) / 2). const marginFromScaling = ( contentHeight * ( 1 - scale ) ) / 2; + useEffect( () => { + if ( iframeDocument && scale !== 1 ) { + iframeDocument.documentElement.style.transform = `scale( ${ scale } )`; + iframeDocument.documentElement.style.marginTop = `${ frameSize }px`; + iframeDocument.documentElement.style.marginBottom = `${ + -marginFromScaling * 2 + frameSize + }px`; + return () => { + iframeDocument.documentElement.style.transform = ''; + iframeDocument.documentElement.style.marginTop = ''; + iframeDocument.documentElement.style.marginBottom = ''; + }; + } + }, [ scale, frameSize, marginFromScaling, iframeDocument ] ); + return ( <> { tabIndex >= 0 && before } @@ -250,19 +292,7 @@ function Iframe( { style={ { border: 0, ...props.style, - height: expand ? contentHeight : props.style?.height, - marginTop: - scale !== 1 - ? -marginFromScaling + frameSize - : props.style?.marginTop, - marginBottom: - scale !== 1 - ? -marginFromScaling + frameSize - : props.style?.marginBottom, - transform: - scale !== 1 - ? `scale( ${ scale } )` - : props.style?.transform, + height: props.style?.height, transition: 'all .3s', } } ref={ useMergeRefs( [ ref, setRef ] ) } diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 6a38e52cbffba1..f0256d0d35d8ff 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -66,10 +66,13 @@ function InserterMenu( insertionIndex: __experimentalInsertionIndex, shouldFocusBlock, } ); - const { showPatterns } = useSelect( + const { isZoomOutMode, showPatterns } = useSelect( ( select ) => { - const { hasAllowedPatterns } = unlock( select( blockEditorStore ) ); + const { hasAllowedPatterns, __unstableGetEditorMode } = unlock( + select( blockEditorStore ) + ); return { + isZoomOutMode: __unstableGetEditorMode() === 'zoom-out', showPatterns: hasAllowedPatterns( destinationRootClientId ), }; }, @@ -77,7 +80,8 @@ function InserterMenu( ); const mediaCategories = useMediaCategories( destinationRootClientId ); - const showMedia = mediaCategories.length > 0; + const showMedia = mediaCategories.length > 0 && ! isZoomOutMode; + const showBlocks = ! isZoomOutMode; const onInsert = useCallback( ( blocks, meta, shouldForceFocusBlock ) => { @@ -249,19 +253,21 @@ function InserterMenu( __experimentalInsertionIndex } showBlockDirectory + showBlocks={ showBlocks } shouldFocusBlock={ shouldFocusBlock } /> ) } { showAsTabs && ( ) } - { ! delayedFilterValue && ! showAsTabs && ( + { ! delayedFilterValue && ! showAsTabs && showBlocks && (
{ blocksTab }
diff --git a/packages/block-editor/src/components/inserter/search-results.js b/packages/block-editor/src/components/inserter/search-results.js index edd99609ea916c..d213bdd1af227a 100644 --- a/packages/block-editor/src/components/inserter/search-results.js +++ b/packages/block-editor/src/components/inserter/search-results.js @@ -50,6 +50,7 @@ function InserterSearchResults( { shouldFocusBlock = true, prioritizePatterns, selectBlockOnInsert, + showBlocks = true, } ) { const debouncedSpeak = useDebounce( speak, 500 ); @@ -167,7 +168,7 @@ function InserterSearchResults( { const hasItems = filteredBlockTypes.length > 0 || filteredBlockPatterns.length > 0; - const blocksUI = !! filteredBlockTypes.length && ( + const blocksUI = showBlocks && !! filteredBlockTypes.length && ( { __( 'Blocks' ) } } > diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index 35c18e1d9acce5..685355a81a2971 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -767,3 +767,13 @@ $block-inserter-tabs-height: 44px; } } } + +.is-zoom-out { + .block-editor-inserter__menu { + display: flex; + } + + .block-editor-inserter__patterns-category-dialog { + position: static; + } +} diff --git a/packages/block-editor/src/components/inserter/tabs.js b/packages/block-editor/src/components/inserter/tabs.js index 4795c3ce4fdc24..ee920df4ca214c 100644 --- a/packages/block-editor/src/components/inserter/tabs.js +++ b/packages/block-editor/src/components/inserter/tabs.js @@ -29,13 +29,14 @@ const mediaTab = { }; function InserterTabs( { + showBlocks = true, showPatterns = false, showMedia = false, onSelect, tabsContents, } ) { const tabs = [ - blocksTab, + showBlocks && blocksTab, showPatterns && patternsTab, showMedia && mediaTab, ].filter( Boolean ); diff --git a/packages/block-editor/src/components/resizable-box-popover/index.js b/packages/block-editor/src/components/resizable-box-popover/index.js index 8a49c1631287a1..61f599663a4f5d 100644 --- a/packages/block-editor/src/components/resizable-box-popover/index.js +++ b/packages/block-editor/src/components/resizable-box-popover/index.js @@ -16,7 +16,7 @@ export default function ResizableBoxPopover( { return ( diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 5f94206c78752f..458f5a96609b65 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -26,6 +26,7 @@ import { getBlockType, store as blocksStore } from '@wordpress/blocks'; */ import { useBlockEditorAutocompleteProps } from '../autocomplete'; import { useBlockEditContext } from '../block-edit'; +import { blockBindingsKey } from '../block-edit/context'; import FormatToolbarContainer from './format-toolbar-container'; import { store as blockEditorStore } from '../../store'; import { useUndoAutomaticChange } from './use-undo-automatic-change'; @@ -117,11 +118,9 @@ export function RichTextWrapper( props = removeNativeProps( props ); const anchorRef = useRef(); - const { - clientId, - isSelected: isBlockSelected, - name: blockName, - } = useBlockEditContext(); + const context = useBlockEditContext(); + const { clientId, isSelected: isBlockSelected, name: blockName } = context; + const blockBindings = context[ blockBindingsKey ]; const selector = ( select ) => { // Avoid subscribing to the block editor store if the block is not // selected. @@ -129,12 +128,10 @@ export function RichTextWrapper( return { isSelected: false }; } - const { getSelectionStart, getSelectionEnd, getBlockAttributes } = + const { getSelectionStart, getSelectionEnd } = select( blockEditorStore ); const selectionStart = getSelectionStart(); const selectionEnd = getSelectionEnd(); - const blockBindings = - getBlockAttributes( clientId )?.metadata?.bindings; let isSelected; @@ -147,48 +144,57 @@ export function RichTextWrapper( isSelected = selectionStart.clientId === clientId; } - // Disable Rich Text editing if block bindings specify that. - let disableBoundBlocks = false; - if ( blockBindings && blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS ) { - const blockTypeAttributes = getBlockType( blockName ).attributes; - const { getBlockBindingsSource } = unlock( select( blocksStore ) ); - for ( const [ attribute, args ] of Object.entries( - blockBindings - ) ) { - if ( - blockTypeAttributes?.[ attribute ]?.source !== 'rich-text' - ) { - break; - } - - // If the source is not defined, or if its value of `lockAttributesEditing` is `true`, disable it. - const blockBindingsSource = getBlockBindingsSource( - args.source - ); - if ( - ! blockBindingsSource || - blockBindingsSource.lockAttributesEditing - ) { - disableBoundBlocks = true; - break; - } - } - } - return { selectionStart: isSelected ? selectionStart.offset : undefined, selectionEnd: isSelected ? selectionEnd.offset : undefined, isSelected, - disableBoundBlocks, }; }; - const { selectionStart, selectionEnd, isSelected, disableBoundBlocks } = - useSelect( selector, [ - clientId, - identifier, - originalIsSelected, - isBlockSelected, - ] ); + const { selectionStart, selectionEnd, isSelected } = useSelect( selector, [ + clientId, + identifier, + originalIsSelected, + isBlockSelected, + ] ); + + const disableBoundBlocks = useSelect( + ( select ) => { + // Disable Rich Text editing if block bindings specify that. + let _disableBoundBlocks = false; + if ( blockBindings && blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS ) { + const blockTypeAttributes = + getBlockType( blockName ).attributes; + const { getBlockBindingsSource } = unlock( + select( blocksStore ) + ); + for ( const [ attribute, args ] of Object.entries( + blockBindings + ) ) { + if ( + blockTypeAttributes?.[ attribute ]?.source !== + 'rich-text' + ) { + break; + } + + // If the source is not defined, or if its value of `lockAttributesEditing` is `true`, disable it. + const blockBindingsSource = getBlockBindingsSource( + args.source + ); + if ( + ! blockBindingsSource || + blockBindingsSource.lockAttributesEditing + ) { + _disableBoundBlocks = true; + break; + } + } + } + + return _disableBoundBlocks; + }, + [ blockBindings, blockName ] + ); const shouldDisableEditing = disableEditing || disableBoundBlocks; diff --git a/packages/block-editor/src/components/rich-text/use-enter.js b/packages/block-editor/src/components/rich-text/use-enter.js index 4daf70e7fa3c74..6b40a82d72d4b2 100644 --- a/packages/block-editor/src/components/rich-text/use-enter.js +++ b/packages/block-editor/src/components/rich-text/use-enter.js @@ -21,6 +21,10 @@ export function useEnter( props ) { propsRef.current = props; return useRefEffect( ( element ) => { function onKeyDown( event ) { + if ( event.target.contentEditable !== 'true' ) { + return; + } + if ( event.defaultPrevented ) { return; } diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 018c9ff9115185..e9281727804f1c 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -924,10 +924,8 @@ export const __unstableExpandSelection = export const mergeBlocks = ( firstBlockClientId, secondBlockClientId ) => ( { registry, select, dispatch } ) => { - const blocks = [ firstBlockClientId, secondBlockClientId ]; - dispatch( { type: 'MERGE_BLOCKS', blocks } ); - - const [ clientIdA, clientIdB ] = blocks; + const clientIdA = firstBlockClientId; + const clientIdB = secondBlockClientId; const blockA = select.getBlock( clientIdA ); const blockAType = getBlockType( blockA.name ); diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index 932e97d95e2f2e..f960363cdb0ddb 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -832,10 +832,6 @@ describe( 'actions', () => { blockB.clientId )( { select, dispatch } ); - expect( dispatch ).toHaveBeenCalledWith( { - type: 'MERGE_BLOCKS', - blocks: [ blockA.clientId, blockB.clientId ], - } ); expect( dispatch.selectBlock ).toHaveBeenCalledWith( 'chicken' ); } ); diff --git a/packages/block-editor/src/utils/calculate-scale.js b/packages/block-editor/src/utils/calculate-scale.js new file mode 100644 index 00000000000000..f07ba24ea341a1 --- /dev/null +++ b/packages/block-editor/src/utils/calculate-scale.js @@ -0,0 +1,20 @@ +const clamp = ( lowerlimit, width, upperlimit ) => { + if ( width < lowerlimit ) return lowerlimit; + if ( width > upperlimit ) return upperlimit; + return width; +}; + +export default function calculateScale( scaleConfig, width ) { + const scaleSlope = + ( scaleConfig.maxScale - scaleConfig.minScale ) / + ( scaleConfig.maxWidth - scaleConfig.minWidth ); + + const scaleIntercept = + scaleConfig.minScale - scaleSlope * scaleConfig.minWidth; + + return clamp( + scaleConfig.maxScale, + scaleSlope * width + scaleIntercept, + scaleConfig.minScale + ); +} diff --git a/packages/block-library/src/cover/edit/block-controls.js b/packages/block-library/src/cover/edit/block-controls.js index 59aaaaffe77d75..c4137ad2a8409a 100644 --- a/packages/block-library/src/cover/edit/block-controls.js +++ b/packages/block-library/src/cover/edit/block-controls.js @@ -8,6 +8,7 @@ import { MediaReplaceFlow, __experimentalBlockAlignmentMatrixControl as BlockAlignmentMatrixControl, __experimentalBlockFullHeightAligmentControl as FullHeightAlignmentControl, + privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; @@ -15,6 +16,9 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { ALLOWED_MEDIA_TYPES } from '../shared'; +import { unlock } from '../../lock-unlock'; + +const { cleanEmptyObject } = unlock( blockEditorPrivateApis ); export default function CoverBlockControls( { attributes, @@ -30,7 +34,10 @@ export default function CoverBlockControls( { const [ prevMinHeightValue, setPrevMinHeightValue ] = useState( minHeight ); const [ prevMinHeightUnit, setPrevMinHeightUnit ] = useState( minHeightUnit ); - const isMinFullHeight = minHeightUnit === 'vh' && minHeight === 100; + const isMinFullHeight = + minHeightUnit === 'vh' && + minHeight === 100 && + ! attributes?.style?.dimensions?.aspectRatio; const toggleMinFullHeight = () => { if ( isMinFullHeight ) { // If there aren't previous values, take the default ones. @@ -51,10 +58,17 @@ export default function CoverBlockControls( { setPrevMinHeightValue( minHeight ); setPrevMinHeightUnit( minHeightUnit ); - // Set full height. + // Set full height, and clear any aspect ratio value. return setAttributes( { minHeight: 100, minHeightUnit: 'vh', + style: cleanEmptyObject( { + ...attributes?.style, + dimensions: { + ...attributes?.style?.dimensions, + aspectRatio: undefined, // Reset aspect ratio when minHeight is set. + }, + } ), } ); }; diff --git a/packages/data/src/redux-store/index.js b/packages/data/src/redux-store/index.js index 7fdc9331a2474b..979c3127b9ed52 100644 --- a/packages/data/src/redux-store/index.js +++ b/packages/data/src/redux-store/index.js @@ -64,12 +64,16 @@ const mapValues = ( obj, callback ) => ] ) ); -// Convert Map objects to plain objects -const mapToObject = ( key, state ) => { +// Convert non serializable types to plain objects +const devToolsReplacer = ( key, state ) => { if ( state instanceof Map ) { return Object.fromEntries( state ); } + if ( state instanceof window.HTMLElement ) { + return null; + } + return state; }; @@ -421,7 +425,7 @@ function instantiateReduxStore( key, options, registry, thunkArgs ) { name: key, instanceId: key, serialize: { - replacer: mapToObject, + replacer: devToolsReplacer, }, } ) ); diff --git a/packages/e2e-test-utils-playwright/src/request-utils/index.ts b/packages/e2e-test-utils-playwright/src/request-utils/index.ts index 5036f3d0e8a97c..f6818945e16936 100644 --- a/packages/e2e-test-utils-playwright/src/request-utils/index.ts +++ b/packages/e2e-test-utils-playwright/src/request-utils/index.ts @@ -93,7 +93,7 @@ class RequestUtils { }, } ); - const requestUtils = new RequestUtils( requestContext, { + const requestUtils = new this( requestContext, { user, storageState, storageStatePath, diff --git a/packages/edit-site/src/components/block-editor/editor-canvas.js b/packages/edit-site/src/components/block-editor/editor-canvas.js index 01bc4cdfa2ddfc..c3ee980515e6c7 100644 --- a/packages/edit-site/src/components/block-editor/editor-canvas.js +++ b/packages/edit-site/src/components/block-editor/editor-canvas.js @@ -26,10 +26,9 @@ import { const { EditorCanvas: EditorCanvasRoot } = unlock( editorPrivateApis ); function EditorCanvas( { enableResizing, settings, children, ...props } ) { - const { hasBlocks, isFocusMode, templateType, canvasMode, isZoomOutMode } = - useSelect( ( select ) => { - const { getBlockCount, __unstableGetEditorMode } = - select( blockEditorStore ); + const { hasBlocks, isFocusMode, templateType, canvasMode } = useSelect( + ( select ) => { + const { getBlockCount } = select( blockEditorStore ); const { getEditedPostType, getCanvasMode } = unlock( select( editSiteStore ) ); @@ -38,11 +37,12 @@ function EditorCanvas( { enableResizing, settings, children, ...props } ) { return { templateType: _templateType, isFocusMode: FOCUSABLE_ENTITIES.includes( _templateType ), - isZoomOutMode: __unstableGetEditorMode() === 'zoom-out', canvasMode: getCanvasMode(), hasBlocks: !! getBlockCount(), }; - }, [] ); + }, + [] + ); const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); const [ isFocused, setIsFocused ] = useState( false ); @@ -107,9 +107,7 @@ function EditorCanvas( { enableResizing, settings, children, ...props } ) { renderAppender={ showBlockAppender } styles={ styles } iframeProps={ { - expand: isZoomOutMode, - scale: isZoomOutMode ? 0.45 : undefined, - frameSize: isZoomOutMode ? 100 : undefined, + shouldZoom: true, className: classnames( 'edit-site-visual-editor__editor-canvas', { diff --git a/packages/edit-site/src/components/block-editor/style.scss b/packages/edit-site/src/components/block-editor/style.scss index 3d042f612f29ed..fa7fd7e13df3e4 100644 --- a/packages/edit-site/src/components/block-editor/style.scss +++ b/packages/edit-site/src/components/block-editor/style.scss @@ -22,7 +22,6 @@ position: relative; height: 100%; display: block; - overflow: hidden; background-color: $gray-300; // Centralize the editor horizontally (flex-direction is column). align-items: center; @@ -62,8 +61,6 @@ .components-resizable-box__container { margin: 0 auto; - // Removing this will cancel the bottom margins in the iframe. - overflow: auto; } &.is-view-mode { diff --git a/packages/edit-site/src/components/global-styles/screen-typography.js b/packages/edit-site/src/components/global-styles/screen-typography.js index f76dc6fb381004..40e2ab08320b75 100644 --- a/packages/edit-site/src/components/global-styles/screen-typography.js +++ b/packages/edit-site/src/components/global-styles/screen-typography.js @@ -9,7 +9,7 @@ import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import TypographyElements from './typogrphy-elements'; +import TypographyElements from './typography-elements'; import FontFamilies from './font-families'; import ScreenHeader from './header'; diff --git a/packages/edit-site/src/components/global-styles/typogrphy-elements.js b/packages/edit-site/src/components/global-styles/typography-elements.js similarity index 100% rename from packages/edit-site/src/components/global-styles/typogrphy-elements.js rename to packages/edit-site/src/components/global-styles/typography-elements.js diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index fcb0a74b0b3b88..d15be016173b03 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -70,6 +70,7 @@ export default function Layout() { const { isDistractionFree, + isZoomOutMode, hasFixedToolbar, hasBlockSelected, canvasMode, @@ -96,6 +97,9 @@ export default function Layout() { 'core', 'distractionFree' ), + isZoomOutMode: + select( blockEditorStore ).__unstableGetEditorMode() === + 'zoom-out', hasBlockSelected: select( blockEditorStore ).getBlockSelectionStart(), }; @@ -172,6 +176,7 @@ export default function Layout() { 'is-full-canvas': canvasMode === 'edit', 'has-fixed-toolbar': hasFixedToolbar, 'is-block-toolbar-visible': hasBlockSelected, + 'is-zoom-out': isZoomOutMode, } ) } > diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/edit-button.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/edit-button.js deleted file mode 100644 index 962062a96da743..00000000000000 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/edit-button.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { pencil } from '@wordpress/icons'; -/** - * Internal dependencies - */ -import SidebarButton from '../sidebar-button'; -import { useLink } from '../routes/link'; -import { NAVIGATION_POST_TYPE } from '../../utils/constants'; - -export default function EditButton( { postId } ) { - const linkInfo = useLink( { - postId, - postType: NAVIGATION_POST_TYPE, - canvas: 'edit', - } ); - return ( - - ); -} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js index 960e0363f2e588..e6348531516f66 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js @@ -10,7 +10,6 @@ import { SidebarNavigationScreenWrapper } from '../sidebar-navigation-screen-nav import ScreenNavigationMoreMenu from './more-menu'; import NavigationMenuEditor from './navigation-menu-editor'; import buildNavigationLabel from '../sidebar-navigation-screen-navigation-menus/build-navigation-label'; -import EditButton from './edit-button'; export default function SingleNavigationMenu( { navigationMenu, @@ -30,7 +29,6 @@ export default function SingleNavigationMenu( { onSave={ handleSave } onDuplicate={ handleDuplicate } /> - } title={ buildNavigationLabel( diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index c86efc10eafb3c..8b6ba0093b4dc3 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -98,7 +98,7 @@ body.js.site-editor-php { } .interface-interface-skeleton__content { - background-color: $gray-900; + background-color: $gray-300; } } diff --git a/packages/element/README.md b/packages/element/README.md index 5636fdda56a525..9ebf2a632d5019 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -247,7 +247,7 @@ This is the same concept as the React Native implementation. _Related_ -- Here is an example of how to use the select method: +- Here is an example of how to use the select method: _Usage_ diff --git a/packages/element/src/platform.js b/packages/element/src/platform.js index c646b6c86d51a2..841cd06e4cabb5 100644 --- a/packages/element/src/platform.js +++ b/packages/element/src/platform.js @@ -17,7 +17,7 @@ const Platform = { * * This is the same concept as the React Native implementation. * - * @see https://facebook.github.io/react-native/docs/platform-specific-code#platform-module + * @see https://reactnative.dev/docs/platform-specific-code#platform-module * * Here is an example of how to use the select method: * @example diff --git a/packages/interactivity/docs/api-reference.md b/packages/interactivity/docs/api-reference.md index b755b124646679..d10d56390b9177 100644 --- a/packages/interactivity/docs/api-reference.md +++ b/packages/interactivity/docs/api-reference.md @@ -39,6 +39,7 @@ DOM elements are connected to data stored in the state and context through direc - [Setting the store](#setting-the-store) - [On the client side](#on-the-client-side) - [On the server side](#on-the-server-side) + - [Store client methods](#store-client-methods) ## The directives @@ -974,3 +975,76 @@ const { state } = store( // The following call works as expected. store( "myPlugin/private", { /* store part */ }, { lock: PRIVATE_LOCK } ); ``` + +### Store client methods + +Apart from the store function, there are also some methods that allows the developer to access data on their store functions. + + - getContext() + - getElement() + +#### getContext() + +Retrieves the context inherited by the element evaluating a function from the store. The returned value depends on the element and the namespace where the function calling `getContext()` exists. + +```php +// render.php +
+ +
+``` + +```js +// store +import { store, getContext } from '@wordpress/interactivity'; + +store( "myPlugin", { + actions: { + log: () => { + const context = getContext(); + // Logs "false" + console.log('context => ', context.isOpen) + }, + }, +}); +``` + +#### getElement() + +Retrieves a representation of the element that the action is bound to or called from. Such representation is read-only, and contains a reference to the DOM element, its props and a local reactive state. +It returns an object with two keys: + +##### ref + +`ref` is the reference to the DOM element as an (HTMLElement)[https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement] + +#### attributes + +`attributes` contains a (Proxy)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy], which adds a getter that allows to reference other store namespaces. Feel free to check the getter in the code. [Link](https://github.com/WordPress/gutenberg/blob/8cb23964d58f3ce5cf6ae1b6f967a4b8d4939a8e/packages/interactivity/src/store.ts#L70) + +Those attributes will contain the directives of that element. In the button example: + +```js +// store +import { store, getContext } from '@wordpress/interactivity'; + +store( "myPlugin", { + actions: { + log: () => { + const element = getElement(); + // Logs "false" + console.log('element attributes => ', element.attributes) + }, + }, +}); +``` + +The code will log: + +```json +{ + "data-wp-on--click": 'actions.increaseCounter', + "children": ['Log'], + "onclick": event => { evaluate(entry, event); } +} +``` diff --git a/packages/react-native-editor/__device-tests__/helpers/test-data.js b/packages/react-native-editor/__device-tests__/helpers/test-data.js index 9816e7dc5354d1..a492b7997f26a3 100644 --- a/packages/react-native-editor/__device-tests__/helpers/test-data.js +++ b/packages/react-native-editor/__device-tests__/helpers/test-data.js @@ -203,6 +203,16 @@ exports.galleryBlock = ``; +exports.galleryBlockTwoImages = ` + +`; + exports.groupNestedStructure = `

Level 1

diff --git a/packages/react-native-editor/__device-tests__/pages/editor-page.js b/packages/react-native-editor/__device-tests__/pages/editor-page.js index ec4483eb704153..4e8f5d9bfd4a5f 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -205,6 +205,36 @@ class EditorPage { return lastElementFound; } + /** + * Selects a block. + * + * @param {import('webdriverio').ChainablePromiseElement} block The block to select. + * @param {Object} options Configuration options. + * @param {Object} [options.offset={ x: 0, y: 0 }] The offset for the click position. + * @param {number|Function} [options.offset.x=0] The x-coordinate offset or a function that calculates the offset based on the block's width. + * @param {number|Function} [options.offset.y=0] The y-coordinate offset or a function that calculates the offset based on the block's height. + * + * @return {import('webdriverio').ChainablePromiseElement} The selected block. + */ + async selectBlock( block, options = {} ) { + const { offset = { x: 0, y: 0 } } = options; + const size = await block.getSize(); + + let offsetX = offset.x; + if ( typeof offset.x === 'function' ) { + offsetX = offset.x( size.width ); + } + + let offsetY = offset.y; + if ( typeof offset.y === 'function' ) { + offsetY = offset.y( size.height ); + } + + await block.click( { x: offsetX, y: offsetY } ); + + return block; + } + async getFirstBlockVisible() { const firstBlockLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, " Block. Row ")]`; return await waitForVisible( this.driver, firstBlockLocator ); @@ -1076,6 +1106,8 @@ const blockNames = { button: 'Button', preformatted: 'Preformatted', unsupported: 'Unsupported', + mediaText: 'Media & Text', + quote: 'Quote', }; module.exports = { setupEditor, blockNames }; diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index f474177dc17b5a..376eaaff4a2051 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -282,6 +282,9 @@ public function test_get_settings_appearance_true_opts_in() { 'typography' => array( 'lineHeight' => true, ), + 'shadow' => array( + 'defaultPresets' => true, + ), 'blocks' => array( 'core/paragraph' => array( 'typography' => array( @@ -321,6 +324,9 @@ public function test_get_settings_appearance_true_opts_in() { 'typography' => array( 'lineHeight' => false, ), + 'shadow' => array( + 'defaultPresets' => true, + ), ), ), ); diff --git a/schemas/json/theme.json b/schemas/json/theme.json index a5b54de97ed70f..a9823f6cc6534b 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -77,7 +77,7 @@ "defaultPresets": { "description": "Allow users to choose shadows from the default shadow presets.", "type": "boolean", - "default": true + "default": false }, "presets": { "description": "Shadow presets for the shadow picker.\nGenerates a single custom property (`--wp--preset--shadow--{slug}`) per preset value.", diff --git a/test/e2e/specs/editor/various/dropdown-menu.spec.js b/test/e2e/specs/editor/various/dropdown-menu.spec.js index 916ef3447d80a4..4c656fd3d6994a 100644 --- a/test/e2e/specs/editor/various/dropdown-menu.spec.js +++ b/test/e2e/specs/editor/various/dropdown-menu.spec.js @@ -13,9 +13,11 @@ test.describe( 'Dropdown Menu', () => { .getByRole( 'region', { name: 'Editor top bar' } ) .getByRole( 'button', { name: 'Options' } ) .click(); - const menuItems = page.locator( - '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]' - ); + const menuItems = page + .getByRole( 'menu', { name: 'Options' } ) + .locator( + '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]' + ); const totalItems = await menuItems.count(); // Catch any issues with the selector, which could cause a false positive test result. diff --git a/test/e2e/specs/site-editor/navigation-editor.spec.js b/test/e2e/specs/site-editor/navigation-editor.spec.js index 344f776c71e027..0761f35535ba8e 100644 --- a/test/e2e/specs/site-editor/navigation-editor.spec.js +++ b/test/e2e/specs/site-editor/navigation-editor.spec.js @@ -19,7 +19,7 @@ test.describe( 'Editing Navigation Menus', () => { requestUtils, editor, } ) => { - await test.step( 'Manually browse to focus mode for a Navigation Menu', async () => { + await test.step( 'Check Navigation block is present and locked', async () => { // create a Navigation Menu called "Test Menu" using the REST API helpers const createdMenu = await requestUtils.createNavigationMenu( { title: 'Primary Menu', @@ -34,69 +34,12 @@ test.describe( 'Editing Navigation Menus', () => { '', } ); - // We could Navigate directly to editing the Navigation Menu but we intentionally do not do this. - // - // Why? To provide coverage for a bug that caused the Navigation Editor behaviours to fail - // only when navigating through the editor screens (rather than going directly to the editor by URL). - // See: https://github.com/WordPress/gutenberg/pull/56856. - // - // Example (what we could do): - // await admin.visitSiteEditor( { - // postId: createdMenu?.id, - // postType: 'wp_navigation', - // } ); - // - await admin.visitSiteEditor(); - - const editorSidebar = page.getByRole( 'region', { - name: 'Navigation', + await admin.visitSiteEditor( { + postId: createdMenu?.id, + postType: 'wp_navigation', + canvas: 'edit', } ); - await editorSidebar - .getByRole( 'button', { - name: 'Navigation', - } ) - .click(); - - // Wait for list of Navigations to appear. - await expect( - editorSidebar.getByRole( 'heading', { - name: 'Navigation', - level: 1, - } ) - ).toBeVisible(); - - await expect( page ).toHaveURL( - `wp-admin/site-editor.php?path=%2Fnavigation` - ); - - await editorSidebar - .getByRole( 'button', { - name: 'Primary Menu', - } ) - .click(); - - await expect( page ).toHaveURL( - `wp-admin/site-editor.php?postId=${ createdMenu?.id }&postType=wp_navigation` - ); - - // Wait for list of Navigations to appear. - await expect( - editorSidebar.getByRole( 'heading', { - name: 'Primary Menu', - level: 1, - } ) - ).toBeVisible(); - - // Switch to editing the Navigation Menu - await editorSidebar - .getByRole( 'link', { - name: 'Edit', - } ) - .click(); - } ); - - await test.step( 'Check Navigation block is present and locked', async () => { // Open List View. await pageUtils.pressKeys( 'access+o' ); diff --git a/test/native/jest.config.js b/test/native/jest.config.js index 4859ea597e0f63..8bd77280dc5509 100644 --- a/test/native/jest.config.js +++ b/test/native/jest.config.js @@ -58,7 +58,7 @@ module.exports = { 'node_modules', ], moduleNameMapper: { - // Mock the CSS modules. See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets + // Mock the CSS modules. See https://jestjs.io/docs/webpack#handling-static-assets '\\.(scss)$': '/test/native/__mocks__/styleMock.js', '\\.(eot|otf|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/test/native/__mocks__/fileMock.js',