/**
* WordPress dependencies
*/
import { Navigator, useNavigator } from '@wordpress/components';
import { getBlockTypes, store as blocksStore } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
// @ts-expect-error: Not typed yet.
import { BlockEditorProvider } from '@wordpress/block-editor';
import { useMemo, useEffect, Fragment } from '@wordpress/element';
import { usePrevious } from '@wordpress/compose';
import {
generateGlobalStyles,
mergeGlobalStyles,
} from '@wordpress/global-styles-engine';
import type { GlobalStylesConfig } from '@wordpress/global-styles-engine';
/**
* Internal dependencies
*/
import { GlobalStylesProvider } from './provider';
import ScreenRoot from './screen-root';
import ScreenBlockList from './screen-block-list';
import ScreenBlock from './screen-block';
import ScreenTypography from './screen-typography';
import ScreenTypographyElement from './screen-typography-element';
import ScreenColors from './screen-colors';
import ScreenColorPalette from './screen-color-palette';
import ScreenBackground from './screen-background';
import { ScreenShadows, ScreenShadowsEdit } from './screen-shadows';
import ScreenLayout from './screen-layout';
import ScreenStyleVariations from './screen-style-variations';
import ScreenCSS from './screen-css';
import ScreenRevisions from './screen-revisions';
import FontSizes from './font-sizes/font-sizes';
import FontSize from './font-sizes/font-size';
interface BlockStylesNavigationScreensProps {
parentMenu: string;
blockStyles: any[];
blockName: string;
}
function BlockStylesNavigationScreens( {
parentMenu,
blockStyles,
blockName,
}: BlockStylesNavigationScreensProps ) {
return (
<>
{ blockStyles.map( ( style, index ) => (
) ) }
>
);
}
interface ContextScreensProps {
name?: string;
parentMenu?: string;
}
interface GlobalStylesNavigationScreenProps {
path: string;
children: React.ReactNode;
}
function ContextScreens( { name, parentMenu = '' }: ContextScreensProps ) {
const blockStyleVariations = useSelect(
( select ) => {
if ( ! name ) {
return [];
}
const { getBlockStyles } = select( blocksStore );
return getBlockStyles( name );
},
[ name ]
);
if ( ! blockStyleVariations?.length ) {
return null;
}
return (
);
}
interface GlobalStylesUIProps {
/** User global styles object (what gets edited) */
value: GlobalStylesConfig;
/** Base global styles object (theme default) */
baseValue: GlobalStylesConfig;
/** Callback when global styles change */
onChange: ( newValue: GlobalStylesConfig ) => void;
/** Current navigation path (optional) */
path?: string;
/** Callback when navigation path changes (optional) */
onPathChange?: ( path: string ) => void;
/** Whether font library is enabled (optional) */
fontLibraryEnabled?: boolean;
/** Server CSS styles for BlockEditorProvider (optional) */
serverCSS?: { isGlobalStyles?: boolean }[];
/** Server settings for BlockEditorProvider (optional) */
serverSettings?: { __unstableResolvedAssets: Record< string, unknown > };
}
export function GlobalStylesUI( {
value,
baseValue,
onChange,
path,
onPathChange,
fontLibraryEnabled = false,
serverCSS,
serverSettings,
}: GlobalStylesUIProps ) {
const blocks = getBlockTypes();
// Compute merged value for CSS generation
const mergedValue = useMemo( () => {
return mergeGlobalStyles( baseValue, value );
}, [ baseValue, value ] );
const [ globalStylesCSS, globalSettings ] = generateGlobalStyles(
mergedValue,
[],
{
styleOptions: { variationStyles: true },
}
);
const styles = useMemo(
() => [ ...( serverCSS ?? [] ), ...( globalStylesCSS ?? [] ) ],
[ serverCSS, globalStylesCSS ]
);
const settings = useMemo( () => {
return {
...serverSettings,
__experimentalFeatures: globalSettings,
styles,
};
}, [ globalSettings, serverSettings, styles ] );
return (
{ ( path || onPathChange ) && (
) }
{ blocks.map( ( block ) => (
) ) }
);
}
function GlobalStylesNavigationScreen( {
path,
children,
}: GlobalStylesNavigationScreenProps ) {
return (
{ children }
);
}
/*
* Component that handles path synchronization between external path prop and Navigator's internal path.
*/
function PathSynchronizer( {
path,
onPathChange,
}: {
path?: string;
onPathChange?: ( path: string ) => void;
} ) {
const navigator = useNavigator();
const { path: childPath } = navigator.location;
const previousParentPath = usePrevious( path );
const previousChildPath = usePrevious( childPath );
useEffect( () => {
// Only sync when parent and child paths are out of sync
if ( path && path !== childPath ) {
// If parent path changed, update the Navigator
if ( path !== previousParentPath ) {
navigator.goTo( path );
}
// If child path changed, notify parent via onPathChange
else if ( childPath !== previousChildPath && onPathChange ) {
onPathChange( childPath ?? '/' );
}
}
}, [
onPathChange,
path,
previousChildPath,
previousParentPath,
childPath,
navigator,
] );
// This component only handles synchronization logic. It doesn't render anything.
// We use it to run the effect inside the Navigator context.
return null;
}