@elementor/editor-editing-panel 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/index.js +657 -486
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +613 -433
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +12 -11
  7. package/src/components/style-sections/border-section/border-radius-field.tsx +4 -4
  8. package/src/components/style-sections/border-section/border-width-field.tsx +4 -4
  9. package/src/components/style-sections/layout-section/align-items-field.tsx +40 -60
  10. package/src/components/style-sections/layout-section/align-self-child-field.tsx +72 -0
  11. package/src/components/style-sections/layout-section/flex-direction-field.tsx +2 -2
  12. package/src/components/style-sections/layout-section/flex-order-field.tsx +14 -8
  13. package/src/components/style-sections/layout-section/flex-size-field.tsx +164 -0
  14. package/src/components/style-sections/layout-section/justify-content-field.tsx +51 -78
  15. package/src/components/style-sections/layout-section/layout-section.tsx +7 -2
  16. package/src/components/style-sections/layout-section/utils/rotated-icon.tsx +52 -0
  17. package/src/components/style-sections/layout-section/wrap-field.tsx +1 -1
  18. package/src/components/style-sections/position-section/position-section.tsx +3 -3
  19. package/src/components/style-sections/typography-section/line-height-field.tsx +21 -0
  20. package/src/components/style-sections/typography-section/text-style-field.tsx +31 -8
  21. package/src/components/style-sections/typography-section/typography-section.tsx +3 -1
  22. package/src/controls-registry/controls-registry.tsx +4 -0
  23. package/src/dynamics/components/dynamic-selection-control.tsx +8 -5
  24. package/src/dynamics/components/dynamic-selection.tsx +10 -8
  25. package/src/dynamics/dynamic-control.tsx +9 -11
  26. package/src/dynamics/utils.ts +20 -3
@@ -1,23 +1,26 @@
1
1
  import * as React from 'react';
2
2
  import { ControlLabel } from '@elementor/editor-controls';
3
+ import { type StringPropValue } from '@elementor/editor-props';
3
4
  import { Divider, Stack } from '@elementor/ui';
4
5
  import { __ } from '@wordpress/i18n';
5
6
 
6
7
  import { useStylesField } from '../../../hooks/use-styles-field';
7
8
  import { AlignItemsField } from './align-items-field';
9
+ import { AlignSelfChild } from './align-self-child-field';
8
10
  import { DisplayField } from './display-field';
9
11
  import { FlexDirectionField } from './flex-direction-field';
10
12
  import { FlexOrderField } from './flex-order-field';
13
+ import { FlexSizeField } from './flex-size-field';
11
14
  import { JustifyContentField } from './justify-content-field';
12
15
  import { WrapField } from './wrap-field';
13
16
 
14
17
  export const LayoutSection = () => {
15
- const [ display ] = useStylesField( 'display' );
18
+ const [ display ] = useStylesField< StringPropValue >( 'display' );
16
19
 
17
20
  return (
18
21
  <Stack gap={ 2 }>
19
22
  <DisplayField />
20
- { 'flex' === display && <FlexFields /> }
23
+ { 'flex' === display?.value && <FlexFields /> }
21
24
  </Stack>
22
25
  );
23
26
  };
@@ -31,6 +34,8 @@ const FlexFields = () => (
31
34
  <WrapField />
32
35
  <Divider />
33
36
  <ControlLabel>{ __( 'Flex child', 'elementor' ) }</ControlLabel>
37
+ <AlignSelfChild />
34
38
  <FlexOrderField />
39
+ <FlexSizeField />
35
40
  </>
36
41
  );
@@ -0,0 +1,52 @@
1
+ import * as React from 'react';
2
+ import { useRef } from 'react';
3
+ import { type StringPropValue } from '@elementor/editor-props';
4
+ import { type ToggleButtonProps, useTheme } from '@elementor/ui';
5
+
6
+ import { useStylesField } from '../../../../hooks/use-styles-field';
7
+ import type { FlexDirection } from '../flex-direction-field';
8
+
9
+ type Props = {
10
+ icon: React.JSX.ElementType;
11
+ size: ToggleButtonProps[ 'size' ];
12
+ isClockwise?: boolean;
13
+ offset?: number;
14
+ };
15
+
16
+ const CLOCKWISE_ANGLES: Record< FlexDirection, number > = {
17
+ row: 0,
18
+ column: 90,
19
+ 'row-reverse': 180,
20
+ 'column-reverse': 270,
21
+ };
22
+
23
+ const COUNTER_CLOCKWISE_ANGLES: Record< FlexDirection, number > = {
24
+ row: 0,
25
+ column: -90,
26
+ 'row-reverse': -180,
27
+ 'column-reverse': -270,
28
+ };
29
+
30
+ export const RotatedIcon = ( { icon: Icon, size, isClockwise = true, offset = 0 }: Props ) => {
31
+ const rotate = useRef( useGetTargetAngle( isClockwise, offset ) );
32
+ rotate.current = useGetTargetAngle( isClockwise, offset, rotate );
33
+
34
+ return <Icon fontSize={ size } sx={ { transition: '.3s', rotate: `${ rotate.current }deg` } } />;
35
+ };
36
+
37
+ const useGetTargetAngle = ( isClockwise: boolean, offset: number, existingRef?: React.MutableRefObject< number > ) => {
38
+ const [ direction ] = useStylesField< StringPropValue >( 'flex-direction' );
39
+ const isRtl = 'rtl' === useTheme().direction;
40
+ const rotationMultiplier = isRtl ? -1 : 1;
41
+ const angleMap = isClockwise ? CLOCKWISE_ANGLES : COUNTER_CLOCKWISE_ANGLES;
42
+
43
+ const currentAngle = existingRef
44
+ ? existingRef.current * rotationMultiplier // Multiply by rotationMultiplier to get the correct angle for RTL, as it will have returned multiplied by this
45
+ : angleMap[ ( direction?.value as FlexDirection ) || 'row' ] + offset;
46
+ const targetAngle = angleMap[ ( direction?.value as FlexDirection ) || 'row' ] + offset;
47
+
48
+ const diffToTargetAngle = ( targetAngle - currentAngle + 360 ) % 360; // Make sure the diff is between 0, 360;
49
+ const formattedDiff = ( ( diffToTargetAngle + 180 ) % 360 ) - 180; // Get the angle to rotate as a value between -180, 180
50
+
51
+ return ( currentAngle + formattedDiff ) * rotationMultiplier;
52
+ };
@@ -24,7 +24,7 @@ const options: ToggleButtonGroupItem< FlexWrap >[] = [
24
24
  },
25
25
  {
26
26
  value: 'wrap-reverse',
27
- label: __( 'Wrap reverse', 'elementor' ),
27
+ label: __( 'Reversed wrap', 'elementor' ),
28
28
  renderContent: ( { size } ) => <ArrowForwardIcon fontSize={ size } />,
29
29
  showTooltip: true,
30
30
  },
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { useCallback } from 'react';
3
- import type { PropValue } from '@elementor/editor-props';
3
+ import type { PropValue, StringPropValue } from '@elementor/editor-props';
4
4
  import { Stack } from '@elementor/ui';
5
5
 
6
6
  import { useStylePropsHistory } from '../../../hooks/use-style-prop-history';
@@ -12,10 +12,10 @@ import { ZIndexField } from './z-index-field';
12
12
  const dimensionsPropKeys = [ 'top', 'bottom', 'left', 'right' ];
13
13
 
14
14
  export const PositionSection = () => {
15
- const [ positionValue ] = useStylesField( 'position' );
15
+ const [ positionValue ] = useStylesField< StringPropValue >( 'position' );
16
16
  usePositionChangeHandler();
17
17
 
18
- const isNotStatic = positionValue && positionValue !== 'static';
18
+ const isNotStatic = positionValue && positionValue?.value !== 'static';
19
19
 
20
20
  return (
21
21
  <Stack gap={ 1.5 }>
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+ import { ControlLabel, SizeControl } from '@elementor/editor-controls';
3
+ import { Grid } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import { StylesField } from '../../../controls-registry/styles-field';
7
+
8
+ export const LineHeightField = () => {
9
+ return (
10
+ <StylesField bind="line-height">
11
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
12
+ <Grid item xs={ 6 }>
13
+ <ControlLabel>{ __( 'Line Height', 'elementor' ) }</ControlLabel>
14
+ </Grid>
15
+ <Grid item xs={ 6 }>
16
+ <SizeControl />
17
+ </Grid>
18
+ </Grid>
19
+ </StylesField>
20
+ );
21
+ };
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { ControlLabel } from '@elementor/editor-controls';
3
+ import { type StringPropValue } from '@elementor/editor-props';
3
4
  import { ItalicIcon, StrikethroughIcon, UnderlineIcon } from '@elementor/icons';
4
5
  import { Grid, ToggleButton as ToggleButtonBase, ToggleButtonGroup, type ToggleButtonProps } from '@elementor/ui';
5
6
  import { __ } from '@wordpress/i18n';
@@ -9,10 +10,32 @@ import { useStylesField } from '../../../hooks/use-styles-field';
9
10
  const buttonSize = 'tiny';
10
11
 
11
12
  export const TextStyleField = () => {
12
- const [ fontStyle, setFontStyle ] = useStylesField< string | null >( 'font-style' );
13
- const [ textDecoration, setTextDecoration ] = useStylesField< string | null >( 'text-decoration' );
13
+ const [ fontStyle, setFontStyle ] = useStylesField< StringPropValue | null >( 'font-style' );
14
+ const [ textDecoration, setTextDecoration ] = useStylesField< StringPropValue | null >( 'text-decoration' );
14
15
 
15
- const formats = [ fontStyle, ...( textDecoration || '' ).split( ' ' ) ];
16
+ const formats = [ fontStyle?.value, ...( textDecoration?.value || '' ).split( ' ' ) ];
17
+
18
+ const handleSetFontStyle = ( newValue: string | null ) => {
19
+ if ( newValue === null ) {
20
+ return setFontStyle( null );
21
+ }
22
+
23
+ setFontStyle( {
24
+ $$type: 'string',
25
+ value: newValue,
26
+ } );
27
+ };
28
+
29
+ const handleSetTextDecoration = ( newValue: string | null ) => {
30
+ if ( newValue === null ) {
31
+ return setTextDecoration( null );
32
+ }
33
+
34
+ setTextDecoration( {
35
+ $$type: 'string',
36
+ value: newValue,
37
+ } );
38
+ };
16
39
 
17
40
  return (
18
41
  <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
@@ -23,7 +46,7 @@ export const TextStyleField = () => {
23
46
  <ToggleButtonGroup value={ formats }>
24
47
  <ToggleButton
25
48
  value="italic"
26
- onChange={ ( v ) => setFontStyle( fontStyle === v ? null : v ) }
49
+ onChange={ ( v ) => handleSetFontStyle( fontStyle?.value === v ? null : v ) }
27
50
  aria-label="italic"
28
51
  sx={ { marginLeft: 'auto' } }
29
52
  >
@@ -31,16 +54,16 @@ export const TextStyleField = () => {
31
54
  </ToggleButton>
32
55
  <ShorthandControl
33
56
  value="line-through"
34
- currentValues={ textDecoration || '' }
35
- updateValues={ setTextDecoration }
57
+ currentValues={ textDecoration?.value || '' }
58
+ updateValues={ handleSetTextDecoration }
36
59
  aria-label="line-through"
37
60
  >
38
61
  <StrikethroughIcon fontSize={ buttonSize } />
39
62
  </ShorthandControl>
40
63
  <ShorthandControl
41
64
  value="underline"
42
- currentValues={ textDecoration || '' }
43
- updateValues={ setTextDecoration }
65
+ currentValues={ textDecoration?.value || '' }
66
+ updateValues={ handleSetTextDecoration }
44
67
  aria-label="underline"
45
68
  >
46
69
  <UnderlineIcon fontSize={ buttonSize } />
@@ -6,6 +6,7 @@ import { FontFamilyField } from './font-family-field';
6
6
  import { FontSizeField } from './font-size-field';
7
7
  import { FontWeightField } from './font-weight-field';
8
8
  import { LetterSpacingField } from './letter-spacing-field';
9
+ import { LineHeightField } from './line-height-field';
9
10
  import { TextAlignmentField } from './text-alignment-field';
10
11
  import { TextColorField } from './text-color-field';
11
12
  import { TextDirectionField } from './text-direction-field';
@@ -21,13 +22,14 @@ export const TypographySection = () => {
21
22
  <FontWeightField />
22
23
  <FontSizeField />
23
24
  <Divider />
25
+ <TextAlignmentField />
24
26
  <TextColorField />
25
27
  <CollapsibleContent>
26
28
  <Stack gap={ 1.5 } sx={ { pt: 1.5 } }>
29
+ <LineHeightField />
27
30
  <LetterSpacingField />
28
31
  <WordSpacingField />
29
32
  <Divider />
30
- <TextAlignmentField />
31
33
  <TextStyleField />
32
34
  <TransformField />
33
35
  <TextDirectionField />
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  type ControlComponent,
3
3
  ImageControl,
4
+ LinkControl,
4
5
  SelectControl,
5
6
  SizeControl,
6
7
  TextAreaControl,
7
8
  TextControl,
9
+ UrlControl,
8
10
  } from '@elementor/editor-controls';
9
11
 
10
12
  export type ControlLayout = 'full' | 'two-columns';
@@ -17,6 +19,8 @@ const controlTypes = {
17
19
  textarea: { component: TextAreaControl, layout: 'full' },
18
20
  size: { component: SizeControl, layout: 'two-columns' },
19
21
  select: { component: SelectControl, layout: 'two-columns' },
22
+ link: { component: LinkControl, layout: 'full' },
23
+ url: { component: UrlControl, layout: 'full' },
20
24
  } as const satisfies ControlRegistry;
21
25
 
22
26
  export type ControlType = keyof typeof controlTypes;
@@ -27,15 +27,17 @@ import { type ControlType, getControlByType } from '../../controls-registry/cont
27
27
  import { usePropValueHistory } from '../../hooks/use-prop-value-history';
28
28
  import { DynamicControl } from '../dynamic-control';
29
29
  import { useDynamicTag } from '../hooks/use-dynamic-tag';
30
- import { type DynamicPropValue, type DynamicTag } from '../types';
30
+ import { type DynamicTag } from '../types';
31
+ import { dynamicPropTypeUtil } from '../utils';
31
32
  import { DynamicSelection } from './dynamic-selection';
32
33
 
33
34
  const SIZE = 'tiny';
34
35
 
35
36
  export const DynamicSelectionControl = () => {
36
- const { bind, value, setValue } = useBoundProp< DynamicPropValue | null >();
37
+ const { setValue: setAnyValue } = useBoundProp();
38
+ const { bind, value } = useBoundProp( dynamicPropTypeUtil );
37
39
  const { getPropValue: getPropValueFromHistory } = usePropValueHistory();
38
- const { name: tagName = '' } = value?.value || {};
40
+ const { name: tagName = '' } = value;
39
41
 
40
42
  const selectionPopoverId = useId();
41
43
  const selectionPopoverState = usePopupState( { variant: 'popover', popupId: selectionPopoverId } );
@@ -43,8 +45,9 @@ export const DynamicSelectionControl = () => {
43
45
  const dynamicTag = useDynamicTag( bind, tagName );
44
46
 
45
47
  const removeDynamicTag = () => {
46
- const propValue = getPropValueFromHistory< DynamicPropValue >( bind );
47
- setValue( propValue ?? null );
48
+ const propValue = getPropValueFromHistory( bind );
49
+
50
+ setAnyValue( propValue ?? null );
48
51
  };
49
52
 
50
53
  if ( ! dynamicTag ) {
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { Fragment, useState } from 'react';
3
3
  import { useBoundProp } from '@elementor/editor-controls';
4
- import { type PropKey, type PropValue } from '@elementor/editor-props';
4
+ import { type PropKey } from '@elementor/editor-props';
5
5
  import { PhotoIcon, SearchIcon } from '@elementor/icons';
6
6
  import {
7
7
  Box,
@@ -20,8 +20,7 @@ import { __ } from '@wordpress/i18n';
20
20
  import { usePropValueHistory } from '../../hooks/use-prop-value-history';
21
21
  import { usePropDynamicTags } from '../hooks/use-prop-dynamic-tags';
22
22
  import { getAtomicDynamicTags } from '../sync/get-atomic-dynamic-tags';
23
- import { type DynamicPropValue } from '../types';
24
- import { isDynamicPropValue } from '../utils';
23
+ import { dynamicPropTypeUtil } from '../utils';
25
24
 
26
25
  type Option = {
27
26
  label: string;
@@ -39,10 +38,13 @@ export type DynamicSelectionProps = {
39
38
  export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
40
39
  const [ searchValue, setSearchValue ] = useState( '' );
41
40
  const { groups: dynamicGroups } = getAtomicDynamicTags() || {};
42
- const { bind, value: currentValue, setValue } = useBoundProp< DynamicPropValue | PropValue >();
41
+
42
+ const { value: anyValue } = useBoundProp();
43
+ const { bind, value: dynamicvalue, setValue } = useBoundProp( dynamicPropTypeUtil );
44
+
43
45
  const { setPropValue: updatePropValueHistory } = usePropValueHistory();
44
46
 
45
- const isCurrentValueDynamic = isDynamicPropValue( currentValue );
47
+ const isCurrentValueDynamic = !! dynamicvalue;
46
48
 
47
49
  const options = useFilteredOptions( bind, searchValue );
48
50
 
@@ -52,10 +54,10 @@ export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
52
54
 
53
55
  const handleSetDynamicTag = ( value: string ) => {
54
56
  if ( ! isCurrentValueDynamic ) {
55
- updatePropValueHistory( bind, currentValue );
57
+ updatePropValueHistory( bind, anyValue );
56
58
  }
57
59
 
58
- setValue( { $$type: 'dynamic', value: { name: value, settings: {} } } );
60
+ setValue( { name: value, settings: {} } );
59
61
 
60
62
  onSelect?.();
61
63
  };
@@ -88,7 +90,7 @@ export const DynamicSelection = ( { onSelect }: DynamicSelectionProps ) => {
88
90
  { dynamicGroups?.[ category ]?.title || category }
89
91
  </ListSubheader>
90
92
  { items.map( ( { value, label: tagLabel } ) => {
91
- const isSelected = isCurrentValueDynamic && value === currentValue?.value?.name;
93
+ const isSelected = isCurrentValueDynamic && value === dynamicvalue?.name;
92
94
 
93
95
  return (
94
96
  <MenuItem
@@ -1,17 +1,17 @@
1
1
  import * as React from 'react';
2
2
  import { BoundPropProvider, useBoundProp } from '@elementor/editor-controls';
3
- import { type PropKey, type PropValue } from '@elementor/editor-props';
3
+ import { isTransformable, type PropKey, type PropValue } from '@elementor/editor-props';
4
4
 
5
5
  import { useDynamicTag } from './hooks/use-dynamic-tag';
6
- import { type DynamicPropValue } from './types';
6
+ import { dynamicPropTypeUtil } from './utils';
7
7
 
8
8
  export type DynamicControlProps = React.PropsWithChildren< {
9
9
  bind: PropKey;
10
10
  } >;
11
11
 
12
12
  export const DynamicControl = ( { bind, children }: DynamicControlProps ) => {
13
- const { value, setValue, bind: propName } = useBoundProp< DynamicPropValue >();
14
- const { name = '', settings } = value?.value ?? {};
13
+ const { value, setValue, bind: propName } = useBoundProp( dynamicPropTypeUtil );
14
+ const { name = '', settings } = value ?? {};
15
15
 
16
16
  const dynamicTag = useDynamicTag( propName, name );
17
17
 
@@ -24,13 +24,11 @@ export const DynamicControl = ( { bind, children }: DynamicControlProps ) => {
24
24
 
25
25
  const setDynamicValue = ( newValue: PropValue ) => {
26
26
  setValue( {
27
- $$type: 'dynamic',
28
- value: {
29
- name,
30
- settings: {
31
- ...settings,
32
- [ bind ]: newValue,
33
- },
27
+ name,
28
+ settings: {
29
+ ...settings,
30
+ // The value inside the dynamic is not a transformable value, so we need to store the whole object.
31
+ [ bind ]: isTransformable( newValue ) ? newValue.value : newValue,
34
32
  },
35
33
  } );
36
34
  };
@@ -1,8 +1,15 @@
1
- import { isTransformable, type PropType, type PropValue, type TransformablePropType } from '@elementor/editor-props';
1
+ import {
2
+ createPropUtils,
3
+ isTransformable,
4
+ type PropType,
5
+ type PropValue,
6
+ type TransformablePropType,
7
+ } from '@elementor/editor-props';
8
+ import { z } from '@elementor/schema';
2
9
 
3
- import { type DynamicPropType, type DynamicPropValue } from './types';
10
+ import { type DynamicPropType } from './types';
4
11
 
5
- const DYNAMIC_PROP_TYPE_KEY = 'dynamic';
12
+ export const DYNAMIC_PROP_TYPE_KEY = 'dynamic';
6
13
 
7
14
  export const isDynamicPropType = ( prop: TransformablePropType ): prop is DynamicPropType =>
8
15
  prop.key === DYNAMIC_PROP_TYPE_KEY;
@@ -20,3 +27,13 @@ export const isDynamicPropValue = ( prop: PropValue ): prop is DynamicPropValue
20
27
  export const supportsDynamic = ( propType: PropType ): boolean => {
21
28
  return !! getDynamicPropType( propType );
22
29
  };
30
+
31
+ export const dynamicPropTypeUtil = createPropUtils(
32
+ DYNAMIC_PROP_TYPE_KEY,
33
+ z.strictObject( {
34
+ name: z.string(),
35
+ settings: z.record( z.any() ).optional(),
36
+ } )
37
+ );
38
+
39
+ export type DynamicPropValue = z.infer< typeof dynamicPropTypeUtil.schema >;