@elementor/editor-editing-panel 4.1.0-manual → 4.2.0-839

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 (33) hide show
  1. package/dist/index.d.mts +169 -1
  2. package/dist/index.d.ts +169 -1
  3. package/dist/index.js +1301 -857
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1145 -683
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +25 -22
  8. package/src/components/css-classes/use-apply-and-unapply-class.ts +10 -0
  9. package/src/components/design-system-import/components/conflict-options.tsx +67 -0
  10. package/src/components/design-system-import/components/trigger-button.tsx +33 -0
  11. package/src/components/design-system-import/hooks/use-dialog-state.ts +24 -0
  12. package/src/components/design-system-import/hooks/use-import-request.ts +38 -0
  13. package/src/components/design-system-import/import-design-system-dialog.tsx +89 -0
  14. package/src/components/design-system-import/import-notifications.tsx +57 -0
  15. package/src/components/design-system-import/types.ts +1 -0
  16. package/src/components/promotions/init.tsx +6 -7
  17. package/src/components/section-content.tsx +9 -2
  18. package/src/components/style-sections/layout-section/align-self-grid-child-field.tsx +74 -0
  19. package/src/components/style-sections/layout-section/display-field.tsx +21 -8
  20. package/src/components/style-sections/layout-section/grid-auto-flow-field.tsx +102 -0
  21. package/src/components/style-sections/layout-section/grid-justify-items-field.tsx +58 -0
  22. package/src/components/style-sections/layout-section/grid-size-field.tsx +96 -0
  23. package/src/components/style-sections/layout-section/grid-span-field.tsx +78 -0
  24. package/src/components/style-sections/layout-section/layout-section.tsx +50 -2
  25. package/src/components/style-sections/position-section/position-field.tsx +2 -6
  26. package/src/components/style-sections/position-section/position-section.tsx +82 -85
  27. package/src/components/style-sections/position-section/z-index-field.tsx +29 -3
  28. package/src/components/style-tab.tsx +3 -0
  29. package/src/contexts/styles-inheritance-context.tsx +16 -2
  30. package/src/controls-registry/conditional-field.tsx +84 -7
  31. package/src/controls-registry/controls-registry.tsx +16 -0
  32. package/src/hooks/use-styles-fields.ts +1 -1
  33. package/src/utils/prop-dependency-utils.ts +88 -12
@@ -0,0 +1,102 @@
1
+ import * as React from 'react';
2
+ import { ControlToggleButtonGroup, type ToggleButtonGroupItem } from '@elementor/editor-controls';
3
+ import { type StringPropValue } from '@elementor/editor-props';
4
+ import { ArrowDownSmallIcon, ArrowRightIcon, LayoutDashboardIcon } from '@elementor/icons';
5
+ import { Grid, ToggleButton, Tooltip, withDirection } from '@elementor/ui';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ import { StylesField } from '../../../controls-registry/styles-field';
9
+ import { useStylesField } from '../../../hooks/use-styles-field';
10
+ import { UiProviders } from '../../../styles-inheritance/components/ui-providers';
11
+ import { StylesFieldLayout } from '../../styles-field-layout';
12
+
13
+ type AutoFlowDirection = 'row' | 'column';
14
+
15
+ const AUTO_FLOW_LABEL = __( 'Auto flow', 'elementor' );
16
+ const DENSE_LABEL = __( 'Dense', 'elementor' );
17
+
18
+ const StartIcon = withDirection( ArrowRightIcon );
19
+
20
+ const directionOptions: ToggleButtonGroupItem< AutoFlowDirection >[] = [
21
+ {
22
+ value: 'row',
23
+ label: __( 'Row', 'elementor' ),
24
+ renderContent: ( { size } ) => <StartIcon fontSize={ size } />,
25
+ showTooltip: true,
26
+ },
27
+ {
28
+ value: 'column',
29
+ label: __( 'Column', 'elementor' ),
30
+ renderContent: ( { size } ) => <ArrowDownSmallIcon fontSize={ size } />,
31
+ showTooltip: true,
32
+ },
33
+ ];
34
+
35
+ const parseAutoFlow = ( value: string | null ): { direction: AutoFlowDirection; dense: boolean } => {
36
+ if ( ! value ) {
37
+ return { direction: 'row', dense: false };
38
+ }
39
+ const dense = value.includes( 'dense' );
40
+ const direction = value.replace( /\s*dense\s*/, '' ).trim() as AutoFlowDirection;
41
+ return { direction: direction || 'row', dense };
42
+ };
43
+
44
+ const composeAutoFlow = ( direction: AutoFlowDirection, dense: boolean ): string => {
45
+ return dense ? `${ direction } dense` : direction;
46
+ };
47
+
48
+ const GridAutoFlowFieldContent = () => {
49
+ const { value, setValue } = useStylesField< StringPropValue | null >( 'grid-auto-flow', {
50
+ history: { propDisplayName: AUTO_FLOW_LABEL },
51
+ } );
52
+
53
+ const { direction, dense } = parseAutoFlow( value?.value ?? null );
54
+
55
+ const handleDirectionChange = ( newDirection: AutoFlowDirection | null ) => {
56
+ if ( ! newDirection ) {
57
+ return;
58
+ }
59
+ setValue( { $$type: 'string', value: composeAutoFlow( newDirection, dense ) } );
60
+ };
61
+
62
+ const handleDenseToggle = () => {
63
+ setValue( { $$type: 'string', value: composeAutoFlow( direction, ! dense ) } );
64
+ };
65
+
66
+ return (
67
+ <StylesFieldLayout label={ AUTO_FLOW_LABEL }>
68
+ <Grid container gap={ 1 } flexWrap="nowrap" alignItems="center" justifyContent="flex-end">
69
+ <Grid item sx={ { width: 64, maxWidth: '100%' } }>
70
+ <ControlToggleButtonGroup
71
+ items={ directionOptions }
72
+ value={ direction }
73
+ onChange={ handleDirectionChange }
74
+ exclusive={ true }
75
+ fullWidth={ true }
76
+ />
77
+ </Grid>
78
+ <Grid item>
79
+ <Tooltip title={ DENSE_LABEL }>
80
+ <ToggleButton
81
+ value="dense"
82
+ selected={ dense }
83
+ onChange={ handleDenseToggle }
84
+ size="tiny"
85
+ aria-label={ DENSE_LABEL }
86
+ >
87
+ <LayoutDashboardIcon fontSize="tiny" />
88
+ </ToggleButton>
89
+ </Tooltip>
90
+ </Grid>
91
+ </Grid>
92
+ </StylesFieldLayout>
93
+ );
94
+ };
95
+
96
+ export const GridAutoFlowField = () => (
97
+ <StylesField bind="grid-auto-flow" propDisplayName={ AUTO_FLOW_LABEL }>
98
+ <UiProviders>
99
+ <GridAutoFlowFieldContent />
100
+ </UiProviders>
101
+ </StylesField>
102
+ );
@@ -0,0 +1,58 @@
1
+ import * as React from 'react';
2
+ import { type ToggleButtonGroupItem, ToggleControl } from '@elementor/editor-controls';
3
+ import {
4
+ LayoutAlignCenterIcon as CenterIcon,
5
+ LayoutAlignLeftIcon,
6
+ LayoutAlignRightIcon,
7
+ LayoutDistributeVerticalIcon as StretchIcon,
8
+ } from '@elementor/icons';
9
+ import { withDirection } from '@elementor/ui';
10
+ import { __ } from '@wordpress/i18n';
11
+
12
+ import { StylesField } from '../../../controls-registry/styles-field';
13
+ import { UiProviders } from '../../../styles-inheritance/components/ui-providers';
14
+ import { StylesFieldLayout } from '../../styles-field-layout';
15
+
16
+ type JustifyItems = 'start' | 'center' | 'end' | 'stretch';
17
+
18
+ const JUSTIFY_ITEMS_LABEL = __( 'Justify items', 'elementor' );
19
+
20
+ const StartIcon = withDirection( LayoutAlignLeftIcon );
21
+ const EndIcon = withDirection( LayoutAlignRightIcon );
22
+
23
+ const options: ToggleButtonGroupItem< JustifyItems >[] = [
24
+ {
25
+ value: 'start',
26
+ label: __( 'Start', 'elementor' ),
27
+ renderContent: ( { size } ) => <StartIcon fontSize={ size } />,
28
+ showTooltip: true,
29
+ },
30
+ {
31
+ value: 'center',
32
+ label: __( 'Center', 'elementor' ),
33
+ renderContent: ( { size } ) => <CenterIcon fontSize={ size } />,
34
+ showTooltip: true,
35
+ },
36
+ {
37
+ value: 'end',
38
+ label: __( 'End', 'elementor' ),
39
+ renderContent: ( { size } ) => <EndIcon fontSize={ size } />,
40
+ showTooltip: true,
41
+ },
42
+ {
43
+ value: 'stretch',
44
+ label: __( 'Stretch', 'elementor' ),
45
+ renderContent: ( { size } ) => <StretchIcon fontSize={ size } />,
46
+ showTooltip: true,
47
+ },
48
+ ];
49
+
50
+ export const GridJustifyItemsField = () => (
51
+ <StylesField bind="justify-items" propDisplayName={ JUSTIFY_ITEMS_LABEL }>
52
+ <UiProviders>
53
+ <StylesFieldLayout label={ JUSTIFY_ITEMS_LABEL }>
54
+ <ToggleControl options={ options } />
55
+ </StylesFieldLayout>
56
+ </UiProviders>
57
+ </StylesField>
58
+ );
@@ -0,0 +1,96 @@
1
+ import * as React from 'react';
2
+ import { useRef } from 'react';
3
+ import { SizeComponent } from '@elementor/editor-controls';
4
+ import { type StringPropValue } from '@elementor/editor-props';
5
+ import { Grid } from '@elementor/ui';
6
+ import { __ } from '@wordpress/i18n';
7
+
8
+ import { StylesField } from '../../../controls-registry/styles-field';
9
+ import { useStylesField } from '../../../hooks/use-styles-field';
10
+ import { StylesFieldLayout } from '../../styles-field-layout';
11
+
12
+ type GridTrackUnit = 'fr' | 'custom';
13
+ type GridTrackValue = { size: number | string; unit: GridTrackUnit };
14
+
15
+ const FR = 'fr' as const;
16
+ const CUSTOM = 'custom' as const;
17
+ const UNITS: GridTrackUnit[] = [ FR, CUSTOM ];
18
+
19
+ const REPEAT_FR_PATTERN = /^repeat\(\s*(\d+)\s*,\s*1fr\s*\)$/;
20
+
21
+ const cssToTrackValue = ( css: string | null ): GridTrackValue | null => {
22
+ if ( ! css ) {
23
+ return null;
24
+ }
25
+ const match = css.match( REPEAT_FR_PATTERN );
26
+ if ( match ) {
27
+ return { size: parseInt( match[ 1 ], 10 ), unit: FR };
28
+ }
29
+ return { size: css, unit: CUSTOM };
30
+ };
31
+
32
+ const trackValueToCss = ( trackValue: GridTrackValue | null ): string | null => {
33
+ if ( ! trackValue || trackValue.size === '' || trackValue.size === null ) {
34
+ return null;
35
+ }
36
+ if ( trackValue.unit === FR ) {
37
+ return `repeat(${ trackValue.size }, 1fr)`;
38
+ }
39
+ return String( trackValue.size );
40
+ };
41
+
42
+ type GridTrackCssProp = 'grid-template-rows' | 'grid-template-columns';
43
+
44
+ type GridTrackFieldProps = {
45
+ cssProp: GridTrackCssProp;
46
+ label: string;
47
+ };
48
+
49
+ const GridTrackField = ( { cssProp, label }: GridTrackFieldProps ) => (
50
+ <StylesField bind={ cssProp } propDisplayName={ label }>
51
+ <GridTrackFieldContent cssProp={ cssProp } label={ label } />
52
+ </StylesField>
53
+ );
54
+
55
+ const GridTrackFieldContent = ( { cssProp, label }: GridTrackFieldProps ) => {
56
+ const { value, setValue } = useStylesField< StringPropValue | null >( cssProp, {
57
+ history: { propDisplayName: label },
58
+ } );
59
+
60
+ const anchorRef = useRef< HTMLDivElement >( null );
61
+ const trackValue = cssToTrackValue( value?.value ?? null );
62
+
63
+ const handleChange = ( newValue: GridTrackValue ) => {
64
+ const css = trackValueToCss( newValue );
65
+ setValue( css ? { $$type: 'string', value: css } : null );
66
+ };
67
+
68
+ return (
69
+ <StylesFieldLayout label={ label } direction="column">
70
+ <div ref={ anchorRef }>
71
+ <SizeComponent
72
+ units={ UNITS as unknown as Parameters< typeof SizeComponent >[ 0 ][ 'units' ] }
73
+ value={
74
+ ( trackValue ?? { size: NaN, unit: FR } ) as Parameters< typeof SizeComponent >[ 0 ][ 'value' ]
75
+ }
76
+ defaultUnit={ FR as Parameters< typeof SizeComponent >[ 0 ][ 'defaultUnit' ] }
77
+ setValue={ handleChange as Parameters< typeof SizeComponent >[ 0 ][ 'setValue' ] }
78
+ onBlur={ () => {} }
79
+ min={ 1 }
80
+ anchorRef={ anchorRef }
81
+ />
82
+ </div>
83
+ </StylesFieldLayout>
84
+ );
85
+ };
86
+
87
+ export const GridSizeFields = () => (
88
+ <Grid container gap={ 2 } flexWrap="nowrap">
89
+ <Grid item xs={ 6 }>
90
+ <GridTrackField cssProp="grid-template-columns" label={ __( 'Columns', 'elementor' ) } />
91
+ </Grid>
92
+ <Grid item xs={ 6 }>
93
+ <GridTrackField cssProp="grid-template-rows" label={ __( 'Rows', 'elementor' ) } />
94
+ </Grid>
95
+ </Grid>
96
+ );
@@ -0,0 +1,78 @@
1
+ import * as React from 'react';
2
+ import { NumberInput } from '@elementor/editor-controls';
3
+ import { type SpanPropValue } from '@elementor/editor-props';
4
+ import { Grid } from '@elementor/ui';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ import { StylesField } from '../../../controls-registry/styles-field';
8
+ import { useStylesField } from '../../../hooks/use-styles-field';
9
+ import { UiProviders } from '../../../styles-inheritance/components/ui-providers';
10
+ import { StylesFieldLayout } from '../../styles-field-layout';
11
+
12
+ type GridSpanCssProp = 'grid-column' | 'grid-row';
13
+
14
+ const MIN_SPAN = 1;
15
+
16
+ type GridSpanFieldProps = {
17
+ cssProp: GridSpanCssProp;
18
+ label: string;
19
+ };
20
+
21
+ const GridSpanFieldContent = ( { cssProp, label }: GridSpanFieldProps ) => {
22
+ const { value, setValue, canEdit } = useStylesField< SpanPropValue | null >( cssProp, {
23
+ history: { propDisplayName: label },
24
+ } );
25
+
26
+ const spanValue = value?.value ?? null;
27
+
28
+ const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
29
+ const raw = event.target.value;
30
+
31
+ if ( raw === '' ) {
32
+ setValue( null );
33
+ return;
34
+ }
35
+
36
+ const num = parseInt( raw, 10 );
37
+
38
+ if ( Number.isNaN( num ) ) {
39
+ return;
40
+ }
41
+
42
+ const clamped = Math.max( num, MIN_SPAN );
43
+ setValue( { $$type: 'span', value: clamped } );
44
+ };
45
+
46
+ return (
47
+ <StylesFieldLayout label={ label } direction="column">
48
+ <NumberInput
49
+ size="tiny"
50
+ type="number"
51
+ fullWidth
52
+ disabled={ ! canEdit }
53
+ value={ spanValue ?? '' }
54
+ onInput={ handleChange }
55
+ inputProps={ { min: MIN_SPAN } }
56
+ />
57
+ </StylesFieldLayout>
58
+ );
59
+ };
60
+
61
+ const GridSpanField = ( { cssProp, label }: GridSpanFieldProps ) => (
62
+ <StylesField bind={ cssProp } propDisplayName={ label }>
63
+ <UiProviders>
64
+ <GridSpanFieldContent cssProp={ cssProp } label={ label } />
65
+ </UiProviders>
66
+ </StylesField>
67
+ );
68
+
69
+ export const GridSpanFields = () => (
70
+ <Grid container gap={ 2 } flexWrap="nowrap">
71
+ <Grid item xs={ 6 }>
72
+ <GridSpanField cssProp="grid-column" label={ __( 'Column Span', 'elementor' ) } />
73
+ </Grid>
74
+ <Grid item xs={ 6 }>
75
+ <GridSpanField cssProp="grid-row" label={ __( 'Row Span', 'elementor' ) } />
76
+ </Grid>
77
+ </Grid>
78
+ );
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
  import { ControlFormLabel } from '@elementor/editor-controls';
3
3
  import { useParentElement } from '@elementor/editor-elements';
4
4
  import { type StringPropValue } from '@elementor/editor-props';
5
+ import { isExperimentActive } from '@elementor/editor-v1-adapters';
5
6
  import { __ } from '@wordpress/i18n';
6
7
 
7
8
  import { useElement } from '../../../contexts/element-context';
@@ -12,33 +13,58 @@ import { SectionContent } from '../../section-content';
12
13
  import { AlignContentField } from './align-content-field';
13
14
  import { AlignItemsField } from './align-items-field';
14
15
  import { AlignSelfChild } from './align-self-child-field';
16
+ import { AlignSelfGridChild } from './align-self-grid-child-field';
15
17
  import { DisplayField, useDisplayPlaceholderValue } from './display-field';
16
18
  import { type FlexDirection, FlexDirectionField } from './flex-direction-field';
17
19
  import { FlexOrderField } from './flex-order-field';
18
20
  import { FlexSizeField } from './flex-size-field';
19
21
  import { GapControlField } from './gap-control-field';
22
+ import { GridAutoFlowField } from './grid-auto-flow-field';
23
+ import { GridJustifyItemsField } from './grid-justify-items-field';
24
+ import { GridSizeFields } from './grid-size-field';
25
+ import { GridSpanFields } from './grid-span-field';
20
26
  import { JustifyContentField } from './justify-content-field';
21
27
  import { WrapField } from './wrap-field';
22
28
 
23
29
  const DISPLAY_LABEL = __( 'Display', 'elementor' );
24
30
  const FLEX_WRAP_LABEL = __( 'Flex wrap', 'elementor' );
31
+ const DEFAULT_PARENT_FLOW_DIRECTION = 'row';
25
32
 
26
33
  export const LayoutSection = () => {
27
34
  const { value: display } = useStylesField< StringPropValue >( 'display', {
28
35
  history: { propDisplayName: DISPLAY_LABEL },
29
36
  } );
30
37
  const displayPlaceholder = useDisplayPlaceholderValue();
38
+ const isGridExperimentActive = isExperimentActive( 'e_css_grid' );
31
39
  const isDisplayFlex = shouldDisplayFlexFields( display, displayPlaceholder as StringPropValue );
40
+ const isDisplayGrid = 'grid' === ( display?.value ?? ( displayPlaceholder as StringPropValue )?.value );
32
41
  const { element } = useElement();
33
42
  const parent = useParentElement( element.id );
34
43
  const parentStyle = useComputedStyle( parent?.id || null );
35
- const parentStyleDirection = parentStyle?.flexDirection ?? 'row';
44
+
45
+ const getParentStyleDirection = () => {
46
+ if ( 'flex' === parentStyle?.display ) {
47
+ return parentStyle?.flexDirection ?? DEFAULT_PARENT_FLOW_DIRECTION;
48
+ }
49
+
50
+ if ( 'grid' === parentStyle?.display ) {
51
+ return parentStyle?.gridAutoFlow ?? DEFAULT_PARENT_FLOW_DIRECTION;
52
+ }
53
+
54
+ return DEFAULT_PARENT_FLOW_DIRECTION;
55
+ };
36
56
 
37
57
  return (
38
58
  <SectionContent>
39
59
  <DisplayField />
40
60
  { isDisplayFlex && <FlexFields /> }
41
- { 'flex' === parentStyle?.display && <FlexChildFields parentStyleDirection={ parentStyleDirection } /> }
61
+ { 'flex' === parentStyle?.display && (
62
+ <FlexChildFields parentStyleDirection={ getParentStyleDirection() } />
63
+ ) }
64
+ { isGridExperimentActive && isDisplayGrid && <GridFields /> }
65
+ { isGridExperimentActive && 'grid' === parentStyle?.display && (
66
+ <GridChildFields parentStyleDirection={ getParentStyleDirection() } />
67
+ ) }
42
68
  </SectionContent>
43
69
  );
44
70
  };
@@ -61,6 +87,18 @@ const FlexFields = () => {
61
87
  );
62
88
  };
63
89
 
90
+ const GridFields = () => (
91
+ <>
92
+ <GridSizeFields />
93
+ <GridAutoFlowField />
94
+ <PanelDivider />
95
+ <GapControlField />
96
+ <PanelDivider />
97
+ <GridJustifyItemsField />
98
+ <AlignItemsField />
99
+ </>
100
+ );
101
+
64
102
  const FlexChildFields = ( { parentStyleDirection }: { parentStyleDirection: string } ) => (
65
103
  <>
66
104
  <PanelDivider />
@@ -71,6 +109,16 @@ const FlexChildFields = ( { parentStyleDirection }: { parentStyleDirection: stri
71
109
  </>
72
110
  );
73
111
 
112
+ const GridChildFields = ( { parentStyleDirection }: { parentStyleDirection: string } ) => (
113
+ <>
114
+ <PanelDivider />
115
+ <ControlFormLabel>{ __( 'Grid Child', 'elementor' ) }</ControlFormLabel>
116
+ <GridSpanFields />
117
+ <AlignSelfGridChild parentStyleDirection={ parentStyleDirection } />
118
+ <FlexOrderField />
119
+ </>
120
+ );
121
+
74
122
  const shouldDisplayFlexFields = ( display: StringPropValue | null, local: StringPropValue ) => {
75
123
  const value = display?.value ?? local?.value;
76
124
 
@@ -15,15 +15,11 @@ const positionOptions = [
15
15
  { label: __( 'Sticky', 'elementor' ), value: 'sticky' },
16
16
  ];
17
17
 
18
- type Props = {
19
- onChange?: ( newValue: string | null, previousValue: string | null | undefined ) => void;
20
- };
21
-
22
- export const PositionField = ( { onChange }: Props ) => {
18
+ export const PositionField = () => {
23
19
  return (
24
20
  <StylesField bind="position" propDisplayName={ POSITION_LABEL }>
25
21
  <StylesFieldLayout label={ POSITION_LABEL }>
26
- <SelectControl options={ positionOptions } onChange={ onChange } />
22
+ <SelectControl options={ positionOptions } />
27
23
  </StylesFieldLayout>
28
24
  </StylesField>
29
25
  );
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react';
2
- import { useCallback, useEffect, useRef } from 'react';
3
- import { type StringPropValue } from '@elementor/editor-props';
2
+ import { useEffect, useRef } from 'react';
3
+ import { type NumberPropValue, type SizePropValue, type StringPropValue } from '@elementor/editor-props';
4
4
  import { useSessionStorage } from '@elementor/session';
5
+ import { styled } from '@elementor/ui';
5
6
  import { __ } from '@wordpress/i18n';
6
7
 
7
8
  import { useStyle } from '../../../contexts/style-context';
@@ -14,112 +15,62 @@ import { OffsetField } from './offset-field';
14
15
  import { PositionField } from './position-field';
15
16
  import { ZIndexField } from './z-index-field';
16
17
 
17
- type DimensionValue =
18
- | {
19
- $$type: 'size';
20
- value: number;
21
- }
22
- | undefined
23
- | null;
24
-
25
- type DimensionsValues = {
26
- 'inset-block-start': DimensionValue;
27
- 'inset-block-end': DimensionValue;
28
- 'inset-inline-start': DimensionValue;
29
- 'inset-inline-end': DimensionValue;
18
+ type DependentValues = {
19
+ 'inset-block-start'?: SizePropValue | null;
20
+ 'inset-block-end'?: SizePropValue | null;
21
+ 'inset-inline-start'?: SizePropValue | null;
22
+ 'inset-inline-end'?: SizePropValue | null;
23
+ 'z-index'?: NumberPropValue | null;
30
24
  };
31
25
 
32
- type PositionDependentValues = DimensionsValues & {
33
- 'z-index': number | undefined | null;
34
- };
35
-
36
- const POSITION_STATIC = 'static' as const;
26
+ const POSITION_STATIC = 'static';
37
27
 
38
28
  const POSITION_LABEL = __( 'Position', 'elementor' );
39
29
  const DIMENSIONS_LABEL = __( 'Dimensions', 'elementor' );
40
30
 
41
- const POSITION_DEPENDENT_PROP_NAMES = [
31
+ const DEPENDENT_PROP_NAMES: Array< keyof DependentValues > = [
42
32
  'inset-block-start',
43
33
  'inset-block-end',
44
34
  'inset-inline-start',
45
35
  'inset-inline-end',
46
36
  'z-index',
47
- ] as const;
48
-
49
- const CLEARED_POSITION_DEPENDENT_VALUES: Record< ( typeof POSITION_DEPENDENT_PROP_NAMES )[ number ], null > = {
50
- 'inset-block-start': null,
51
- 'inset-block-end': null,
52
- 'inset-inline-start': null,
53
- 'inset-inline-end': null,
54
- 'z-index': null,
55
- };
37
+ ];
56
38
 
57
39
  export const PositionSection = () => {
58
- const { value: positionValue } = useStylesField< StringPropValue >( 'position', {
59
- history: { propDisplayName: POSITION_LABEL },
60
- } );
61
- const { values: positionDependentValues, setValues: setPositionDependentValues } =
62
- useStylesFields< PositionDependentValues >( [ ...POSITION_DEPENDENT_PROP_NAMES ] );
63
-
64
- const [ dimensionsValuesFromHistory, updateDimensionsHistory, clearDimensionsHistory ] = usePersistDimensions();
65
-
66
- const clearPositionDependentProps = useCallback( () => {
67
- const dimensions: DimensionsValues = {
68
- 'inset-block-start': positionDependentValues?.[ 'inset-block-start' ],
69
- 'inset-block-end': positionDependentValues?.[ 'inset-block-end' ],
70
- 'inset-inline-start': positionDependentValues?.[ 'inset-inline-start' ],
71
- 'inset-inline-end': positionDependentValues?.[ 'inset-inline-end' ],
72
- };
73
- const meta = { history: { propDisplayName: DIMENSIONS_LABEL } };
74
- const hasValuesToClear =
75
- Object.values( dimensions ).some( ( v ) => v !== null ) || positionDependentValues?.[ 'z-index' ] !== null;
40
+ const { value: position } = useStylesField< StringPropValue >( 'position', withHistoryLabel( POSITION_LABEL ) );
41
+ const positionPrevRef = useRef( position );
42
+ const { values: dependentValues, setValues: setDependentValues } =
43
+ useStylesFields< DependentValues >( DEPENDENT_PROP_NAMES );
44
+
45
+ const [ savedDependentValues, saveToHistory, clearHistory ] = usePersistDimensions();
76
46
 
77
- if ( hasValuesToClear ) {
78
- updateDimensionsHistory( dimensions );
79
- setPositionDependentValues( CLEARED_POSITION_DEPENDENT_VALUES, meta );
47
+ useEffect( () => {
48
+ if ( position && position?.value === POSITION_STATIC && hasDependentValues( dependentValues ) ) {
49
+ saveToHistory( extractDimensions( dependentValues ) );
80
50
  }
81
- }, [ positionDependentValues, updateDimensionsHistory, setPositionDependentValues ] );
82
51
 
83
- const clearPositionDependentPropsRef = useRef( clearPositionDependentProps );
84
- clearPositionDependentPropsRef.current = clearPositionDependentProps;
52
+ if ( positionPrevRef.current?.value === POSITION_STATIC ) {
53
+ setDependentValues( { ...savedDependentValues }, withHistoryLabel( DIMENSIONS_LABEL ) );
85
54
 
86
- useEffect( () => {
87
- if ( positionValue?.value === POSITION_STATIC || positionValue === null ) {
88
- clearPositionDependentPropsRef.current();
55
+ clearHistory();
89
56
  }
90
- }, [ positionValue ] );
91
-
92
- const onPositionChange = ( newPosition: string | null, previousPosition: string | null | undefined ) => {
93
- const meta = { history: { propDisplayName: DIMENSIONS_LABEL } };
94
-
95
- if ( newPosition === POSITION_STATIC ) {
96
- clearPositionDependentProps();
97
- } else if ( previousPosition === POSITION_STATIC ) {
98
- if ( dimensionsValuesFromHistory ) {
99
- setPositionDependentValues(
100
- { ...dimensionsValuesFromHistory, 'z-index': undefined } as PositionDependentValues,
101
- meta
102
- );
103
- clearDimensionsHistory();
104
- }
57
+
58
+ if ( ( ! position || position?.value === POSITION_STATIC ) && dependentValues?.[ 'z-index' ] ) {
59
+ setDependentValues( { 'z-index': null }, withHistoryLabel( DIMENSIONS_LABEL ) );
105
60
  }
106
- };
107
61
 
108
- const isNotStatic = positionValue && positionValue?.value !== POSITION_STATIC;
62
+ positionPrevRef.current = position;
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ }, [ position?.value ] );
109
65
 
110
66
  return (
111
- <SectionContent>
112
- <PositionField onChange={ onPositionChange } />
113
- { isNotStatic ? (
114
- <>
115
- <DimensionsField />
116
- <ZIndexField />
117
- </>
118
- ) : null }
119
-
67
+ <StyledSectionContent>
68
+ <PositionField />
69
+ <DimensionsField />
70
+ <ZIndexField disabled={ ! position || position?.value === POSITION_STATIC } />
120
71
  <PanelDivider />
121
72
  <OffsetField />
122
- </SectionContent>
73
+ </StyledSectionContent>
123
74
  );
124
75
  };
125
76
 
@@ -128,5 +79,51 @@ const usePersistDimensions = () => {
128
79
  const styleVariantPath = `styles/${ styleDefID }/${ meta.breakpoint || 'desktop' }/${ meta.state || 'null' }`;
129
80
  const dimensionsPath = `${ styleVariantPath }/dimensions`;
130
81
 
131
- return useSessionStorage< DimensionsValues >( dimensionsPath );
82
+ return useSessionStorage< DependentValues >( dimensionsPath );
132
83
  };
84
+
85
+ const withHistoryLabel = ( name: string ) => {
86
+ return {
87
+ history: { propDisplayName: name },
88
+ };
89
+ };
90
+
91
+ const hasDependentValues = ( values?: DependentValues | null ) => {
92
+ if ( ! values ) {
93
+ return false;
94
+ }
95
+
96
+ const dimensions = extractDimensions( values );
97
+
98
+ return Object.values( dimensions ).some( ( v ) => v !== null );
99
+ };
100
+
101
+ const extractDimensions = ( values: DependentValues | null ): DependentValues => {
102
+ return DEPENDENT_PROP_NAMES.reduce( ( acc, key ) => {
103
+ return {
104
+ ...acc,
105
+ [ key ]: values?.[ key ] ?? null,
106
+ };
107
+ }, {} );
108
+ };
109
+
110
+ const StyledSectionContent = styled( SectionContent, {
111
+ shouldForwardProp: ( prop ) => prop !== 'gap',
112
+ } )< { gap?: number } >( ( { gap = 2, theme } ) => ( {
113
+ gap: 0,
114
+ '& > *': {
115
+ marginBottom: theme.spacing( gap ),
116
+ },
117
+ '& > *:last-child': {
118
+ marginBottom: 0,
119
+ },
120
+ '& > .MuiStack-root': {
121
+ marginBottom: 0,
122
+ },
123
+ '& > .MuiStack-root:has(> *)': {
124
+ marginBottom: theme.spacing( gap ),
125
+ },
126
+ '& > .MuiDivider-root': {
127
+ marginBottom: theme.spacing( gap ),
128
+ },
129
+ } ) );