Skip to content
Closed

fu #5

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions modules/atomic-widgets/styles/styles-renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,17 @@ private function get_base_selector( array $style_def ): ?string {
}

private function variant_to_css_string( string $base_selector, array $variant ): string {
$css = $this->props_to_css_string( $variant['props'] );
$css = $this->props_to_css_string( $variant['props'] ) ?? '';
$custom_css = isset( $variant['customCss']['raw'] ) ? $variant['customCss']['raw'] : '';

if ( ! $css ) {
if ( ! $css && ! $custom_css ) {
return '';
}

$state = isset( $variant['meta']['state'] ) ? ':' . $variant['meta']['state'] : '';
$selector = $base_selector . $state;

$style_declaration = $selector . '{' . $css . '}';
$style_declaration = $selector . '{' . $css . $custom_css . '}';

if ( isset( $variant['meta']['breakpoint'] ) ) {
$style_declaration = $this->wrap_with_media_query( $variant['meta']['breakpoint'], $style_declaration );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export function createStylesRenderer( { resolve, breakpoints, selectorPrefix = '
return async ( { styles, signal }: StyleRendererArgs ): Promise< StyleItem[] > => {
const stylesCssPromises = styles.map( async ( style ) => {
const variantCssPromises = Object.values( style.variants ).map( async ( variant ) => {
const css = await propsToCss( { props: variant.props, resolve, signal } );
const css =
( await propsToCss( { props: variant.props, resolve, signal } ) ) +
( variant.customCss?.raw ?? '' );

return createStyleWrapper()
.for( style.cssName, style.type )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ export const undoablePasteElementStyle = () =>
};

if ( styleId ) {
newStyle.variants.forEach( ( { meta, props } ) => {
newStyle.variants.forEach( ( { meta, props, customCss } ) => {
updateElementStyle( {
elementId,
styleId,
meta,
props,
customCss,
} );
} );
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';
import { TextField } from '@elementor/ui';

import { useCustomCss } from '../hooks/use-custom-css';
import { SectionContent } from './section-content';

export const CustomCss = () => {
const { customCss, setCustomCss } = useCustomCss();

return (
<SectionContent>
<TextField
value={ customCss?.raw || '' }
onChange={ ( ev: React.ChangeEvent< HTMLInputElement > ) =>
setCustomCss( ev.target.value, { history: { propDisplayName: 'Custom CSS' } } )
}
/>
</SectionContent>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { StyleProvider } from '../contexts/style-context';
import { StyleInheritanceProvider } from '../contexts/styles-inheritance-context';
import { useActiveStyleDefId } from '../hooks/use-active-style-def-id';
import { CssClassSelector } from './css-classes/css-class-selector';
import { CustomCss } from './custom-css';
import { SectionsList } from './sections-list';
import { BackgroundSection } from './style-sections/background-section/background-section';
import { BorderSection } from './style-sections/border-section/border-section';
Expand Down Expand Up @@ -157,6 +158,14 @@ export const StyleTab = () => {
} }
fields={ [ 'box-shadow', 'opacity', 'transform', 'filter', 'backdrop-filter' ] }
/>
<StyleTabSection
section={ {
component: CustomCss,
name: 'Custom CSS',
title: __( 'Custom CSS', 'elementor' ),
} }
fields={ [ 'box-shadow', 'opacity', 'transform', 'filter', 'backdrop-filter' ] }
/>
</SectionsList>
<Box sx={ { height: '150px' } } />
</StyleInheritanceProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { useMemo } from 'react';
import { createElementStyle, deleteElementStyle, type ElementID } from '@elementor/editor-elements';
import {
type CustomCss,
getVariantByMeta,
type StyleDefinition,
type StyleDefinitionVariant,
} from '@elementor/editor-styles';
import { ELEMENTS_STYLES_RESERVED_LABEL, type StylesProvider } from '@elementor/editor-styles-repository';
import { isExperimentActive, undoable } from '@elementor/editor-v1-adapters';

import { useClassesProp } from '../contexts/classes-prop-context';
import { useElement } from '../contexts/element-context';
import { useStyle } from '../contexts/style-context';
import { StylesProviderCannotUpdatePropsError } from '../errors';
import { EXPERIMENTAL_FEATURES } from '../sync/experiments-flags';
import { getSubtitle, getTitle, HISTORY_DEBOUNCE_WAIT } from './use-styles-fields';
import { useStylesRerender } from './use-styles-rerender';

export const useCustomCss = () => {
const {
element: { id: elementId },
} = useElement();
const { id: styleId, meta, provider } = useStyle();
const style = provider?.actions.get( styleId, { elementId } );

const undoableUpdateStyle = useUndoableUpdateCustomCss( { elementId, meta } );

useStylesRerender();

const variant = style ? getVariantByMeta( style, meta ) : null;
const customCss = variant?.customCss?.raw ? variant?.customCss : null;

const setCustomCss = (
raw: string,
{ history: { propDisplayName } }: { history: { propDisplayName: string } }
) => {
if ( ! styleId ) {
undoableUpdateStyle( { styleId: null, provider: null, customCss: { raw }, propDisplayName } );
} else {
undoableUpdateStyle( { styleId, provider, customCss: { raw }, propDisplayName } );
}
};

return {
customCss,
setCustomCss,
};
};

type UpdateStyleArgs = {
styleId: StyleDefinition[ 'id' ];
provider: StylesProvider;
customCss: CustomCss | null;
propDisplayName: string;
};

type CreateStyleArgs = {
styleId: null;
provider: null;
customCss: CustomCss | null;
propDisplayName: string;
};

type UpdateStyleReturn = {
styleId: StyleDefinition[ 'id' ];
provider: StylesProvider;
prevCustomCss: CustomCss | null;
};

type CreateStyleReturn = {
createdStyleId: StyleDefinition[ 'id' ];
};

type UndoableUpdateStylePayload = UpdateStyleArgs | CreateStyleArgs;
type UndoableUpdateStyleReturn = UpdateStyleReturn | CreateStyleReturn;

export function useUndoableUpdateCustomCss( {
elementId,
meta: { breakpoint, state },
}: {
elementId: ElementID;
meta: StyleDefinitionVariant[ 'meta' ];
} ) {
const classesProp = useClassesProp();

return useMemo( () => {
const isVersion331Active = isExperimentActive( EXPERIMENTAL_FEATURES.V_3_31 );

const meta = { breakpoint, state };

const createStyleArgs = { elementId, classesProp, meta, label: ELEMENTS_STYLES_RESERVED_LABEL };

return undoable(
{
do: ( payload: UndoableUpdateStylePayload ): UndoableUpdateStyleReturn => {
if ( shouldCreateNewLocalStyle( payload ) ) {
return createLocalStyle( payload as CreateStyleArgs );
}
return updateStyleCustomCss( payload as UpdateStyleArgs );
},
undo: ( payload: UndoableUpdateStylePayload, doReturn: UndoableUpdateStyleReturn ) => {
const wasLocalStyleCreated = shouldCreateNewLocalStyle( payload );

if ( wasLocalStyleCreated ) {
return undoCreateLocalStyle( payload as CreateStyleArgs, doReturn as CreateStyleReturn );
}
return undoUpdateStyleCustomCss( payload as UpdateStyleArgs, doReturn as UpdateStyleReturn );
},
redo: ( payload: UndoableUpdateStylePayload, doReturn: UndoableUpdateStyleReturn ) => {
const wasLocalStyleCreated = shouldCreateNewLocalStyle( payload );

if ( wasLocalStyleCreated ) {
return createLocalStyle( payload as CreateStyleArgs, doReturn as CreateStyleReturn );
}
return updateStyleCustomCss( payload as UpdateStyleArgs );
},
},
{
title: ( { provider, styleId } ) => getTitle( { provider, styleId, elementId, isVersion331Active } ),
subtitle: ( { provider, styleId, propDisplayName } ) =>
getSubtitle( { provider, styleId, elementId, isVersion331Active, propDisplayName } ),
debounce: { wait: HISTORY_DEBOUNCE_WAIT },
}
);

function shouldCreateNewLocalStyle( payload: UndoableUpdateStylePayload ) {
// If styleId and provider are nullish, it means that it's a local style that haven't been created yet.
// Local styles are created only when the user starts editing a style.
return ! payload.styleId && ! payload.provider;
}

function createLocalStyle( { customCss }: CreateStyleArgs, redoArgs?: CreateStyleReturn ): CreateStyleReturn {
const createdStyle = createElementStyle( {
...createStyleArgs,
props: {},
customCss: customCss ?? null,
styleId: redoArgs?.createdStyleId,
} );

return { createdStyleId: createdStyle };
}

function undoCreateLocalStyle( _: UndoableUpdateStylePayload, { createdStyleId }: CreateStyleReturn ) {
deleteElementStyle( elementId, createdStyleId );
}

function updateStyleCustomCss( { provider, styleId, customCss }: UpdateStyleArgs ): UpdateStyleReturn {
if ( ! provider.actions.updateCustomCss ) {
throw new StylesProviderCannotUpdatePropsError( {
context: { providerKey: provider.getKey() },
} );
}

const style = provider.actions.get( styleId, { elementId } );
const prevCustomCss = getCurrentCustomCss( style, meta );

provider.actions.updateCustomCss( { id: styleId, meta, customCss }, { elementId } );

return { styleId, provider, prevCustomCss };
}

function undoUpdateStyleCustomCss(
_: UndoableUpdateStylePayload,
{ styleId, provider, prevCustomCss }: UpdateStyleReturn
) {
provider.actions.updateCustomCss?.( { id: styleId, meta, customCss: prevCustomCss }, { elementId } );
}
}, [ elementId, breakpoint, state, classesProp ] );
}

function getCurrentCustomCss( style: StyleDefinition | null, meta: StyleDefinitionVariant[ 'meta' ] ) {
if ( ! style ) {
return null;
}

const variant = getVariantByMeta( style, meta );

return variant?.customCss ?? null;
}
Loading