@elementor/editor-editing-panel 1.0.0 → 1.2.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 (120) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/index.d.mts +10 -19
  3. package/dist/index.d.ts +10 -19
  4. package/dist/index.js +1539 -1754
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +1534 -1723
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +13 -13
  9. package/src/components/add-or-remove-content.tsx +3 -3
  10. package/src/components/collapse-icon.tsx +12 -0
  11. package/src/components/collapsible-content.tsx +5 -14
  12. package/src/components/collapsible-field.tsx +5 -3
  13. package/src/components/css-class-selector.tsx +131 -0
  14. package/src/components/editing-panel-hooks.tsx +2 -0
  15. package/src/components/editing-panel-tabs.tsx +23 -13
  16. package/src/components/editing-panel.tsx +9 -6
  17. package/src/components/multi-combobox/index.ts +3 -0
  18. package/src/components/multi-combobox/multi-combobox.tsx +122 -0
  19. package/src/components/multi-combobox/types.ts +28 -0
  20. package/src/components/multi-combobox/use-combobox-actions.ts +62 -0
  21. package/src/components/section.tsx +37 -0
  22. package/src/components/sections-list.tsx +6 -0
  23. package/src/components/settings-tab.tsx +11 -11
  24. package/src/components/style-sections/background-section/background-color-field.tsx +4 -4
  25. package/src/components/style-sections/background-section/background-section.tsx +9 -7
  26. package/src/components/style-sections/border-section/border-color-field.tsx +4 -4
  27. package/src/components/style-sections/border-section/border-field.tsx +4 -3
  28. package/src/components/style-sections/border-section/border-radius-field.tsx +4 -3
  29. package/src/components/style-sections/border-section/border-section.tsx +7 -10
  30. package/src/components/style-sections/border-section/border-style-field.tsx +4 -4
  31. package/src/components/style-sections/border-section/border-width-field.tsx +4 -3
  32. package/src/components/style-sections/effects-section/effects-section.tsx +7 -10
  33. package/src/components/style-sections/layout-section/align-items-field.tsx +92 -0
  34. package/src/components/style-sections/layout-section/display-field.tsx +32 -0
  35. package/src/components/style-sections/layout-section/flex-direction-field.tsx +64 -0
  36. package/src/components/style-sections/layout-section/flex-order-field.tsx +114 -0
  37. package/src/components/style-sections/layout-section/justify-content-field.tsx +109 -0
  38. package/src/components/style-sections/layout-section/layout-section.tsx +36 -0
  39. package/src/components/style-sections/layout-section/wrap-field.tsx +52 -0
  40. package/src/components/style-sections/position-section/dimensions-field.tsx +6 -6
  41. package/src/components/style-sections/position-section/position-field.tsx +4 -4
  42. package/src/components/style-sections/position-section/position-section.tsx +45 -15
  43. package/src/components/style-sections/position-section/z-index-field.tsx +4 -4
  44. package/src/components/style-sections/size-section/overflow-field.tsx +8 -8
  45. package/src/components/style-sections/size-section/size-section.tsx +33 -26
  46. package/src/components/style-sections/spacing-section/spacing-section.tsx +11 -13
  47. package/src/components/style-sections/typography-section/font-family-field.tsx +40 -0
  48. package/src/components/style-sections/typography-section/font-size-field.tsx +4 -4
  49. package/src/components/style-sections/typography-section/font-weight-field.tsx +4 -4
  50. package/src/components/style-sections/typography-section/letter-spacing-field.tsx +4 -4
  51. package/src/components/style-sections/typography-section/text-alignment-field.tsx +9 -9
  52. package/src/components/style-sections/typography-section/text-color-field.tsx +4 -4
  53. package/src/components/style-sections/typography-section/text-direction-field.tsx +7 -7
  54. package/src/components/style-sections/typography-section/text-stroke-field.tsx +42 -7
  55. package/src/components/style-sections/typography-section/text-style-field.tsx +5 -4
  56. package/src/components/style-sections/typography-section/transform-field.tsx +23 -9
  57. package/src/components/style-sections/typography-section/typography-section.tsx +26 -27
  58. package/src/components/style-sections/typography-section/word-spacing-field.tsx +4 -4
  59. package/src/components/style-tab.tsx +67 -31
  60. package/src/contexts/classes-prop-context.tsx +1 -1
  61. package/src/contexts/element-context.tsx +2 -2
  62. package/src/contexts/style-context.tsx +6 -5
  63. package/src/control-replacement.tsx +1 -1
  64. package/src/controls-actions.ts +3 -2
  65. package/src/controls-registry/control-type-container.tsx +3 -2
  66. package/src/controls-registry/control.tsx +2 -1
  67. package/src/controls-registry/controls-registry.tsx +8 -1
  68. package/src/controls-registry/settings-field.tsx +5 -4
  69. package/src/controls-registry/styles-field.tsx +3 -2
  70. package/src/dynamics/components/dynamic-selection-control.tsx +15 -14
  71. package/src/dynamics/components/dynamic-selection.tsx +9 -8
  72. package/src/dynamics/dynamic-control.tsx +4 -4
  73. package/src/dynamics/hooks/use-dynamic-tag.ts +3 -2
  74. package/src/dynamics/hooks/use-prop-dynamic-action.tsx +6 -5
  75. package/src/dynamics/hooks/use-prop-dynamic-tags.ts +3 -2
  76. package/src/dynamics/init.ts +5 -3
  77. package/src/dynamics/sync/get-elementor-config.ts +1 -1
  78. package/src/dynamics/types.ts +2 -2
  79. package/src/dynamics/utils.ts +3 -2
  80. package/src/hooks/use-close-editor-panel.ts +23 -0
  81. package/src/hooks/use-direction.ts +13 -0
  82. package/src/hooks/use-open-editor-panel.ts +4 -3
  83. package/src/hooks/use-prop-value-history.ts +45 -0
  84. package/src/hooks/use-style-prop-history.ts +75 -0
  85. package/src/hooks/use-styles-field.ts +25 -4
  86. package/src/index.ts +1 -1
  87. package/src/init.ts +5 -4
  88. package/src/panel.ts +1 -0
  89. package/src/popover-action.tsx +1 -1
  90. package/src/sync/enqueue-font.ts +7 -0
  91. package/src/sync/get-elementor-config.ts +7 -0
  92. package/src/sync/{should-use-v2-panel.ts → is-atomic-widget-selected.ts} +1 -1
  93. package/src/sync/types.ts +20 -21
  94. package/src/components/accordion-section.tsx +0 -26
  95. package/src/components/control-label.tsx +0 -10
  96. package/src/controls/bound-prop-context.tsx +0 -30
  97. package/src/controls/components/control-toggle-button-group.tsx +0 -68
  98. package/src/controls/components/repeater.tsx +0 -197
  99. package/src/controls/components/text-field-inner-selection.tsx +0 -75
  100. package/src/controls/control-actions/control-actions-context.tsx +0 -27
  101. package/src/controls/control-actions/control-actions-menu.ts +0 -7
  102. package/src/controls/control-actions/control-actions.tsx +0 -31
  103. package/src/controls/controls/box-shadow-repeater-control.tsx +0 -210
  104. package/src/controls/controls/color-control.tsx +0 -25
  105. package/src/controls/controls/equal-unequal-sizes-control.tsx +0 -196
  106. package/src/controls/controls/image-control.tsx +0 -58
  107. package/src/controls/controls/image-media-control.tsx +0 -64
  108. package/src/controls/controls/linked-dimensions-control.tsx +0 -139
  109. package/src/controls/controls/number-control.tsx +0 -29
  110. package/src/controls/controls/select-control.tsx +0 -30
  111. package/src/controls/controls/size-control.tsx +0 -71
  112. package/src/controls/controls/stroke-control.tsx +0 -105
  113. package/src/controls/controls/text-area-control.tsx +0 -31
  114. package/src/controls/controls/text-control.tsx +0 -17
  115. package/src/controls/controls/toggle-control.tsx +0 -26
  116. package/src/controls/create-control-replacement.tsx +0 -53
  117. package/src/controls/create-control.tsx +0 -40
  118. package/src/controls/hooks/use-sync-external-state.tsx +0 -51
  119. package/src/controls/index.ts +0 -24
  120. package/src/dynamics/hooks/use-prop-value-history.ts +0 -26
@@ -1,19 +1,20 @@
1
1
  import * as React from 'react';
2
- import { Stack } from '@elementor/ui';
2
+ import { ControlLabel } from '@elementor/editor-controls';
3
3
  import { type Control } from '@elementor/editor-elements';
4
- import { SettingsField } from '../controls-registry/settings-field';
5
- import { AccordionSection } from './accordion-section';
4
+
5
+ import { useElement } from '../contexts/element-context';
6
6
  import { Control as BaseControl } from '../controls-registry/control';
7
- import { ControlType, getControlByType } from '../controls-registry/controls-registry';
8
7
  import { ControlTypeContainer } from '../controls-registry/control-type-container';
9
- import { ControlLabel } from './control-label';
10
- import { useElement } from '../contexts/element-context';
8
+ import { type ControlType, getControlByType } from '../controls-registry/controls-registry';
9
+ import { SettingsField } from '../controls-registry/settings-field';
10
+ import { Section } from './section';
11
+ import { SectionsList } from './sections-list';
11
12
 
12
13
  export const SettingsTab = () => {
13
14
  const { elementType } = useElement();
14
15
 
15
16
  return (
16
- <Stack>
17
+ <SectionsList>
17
18
  { elementType.controls.map( ( { type, value }, index ) => {
18
19
  if ( type === 'control' ) {
19
20
  return <Control key={ value.bind } control={ value } />;
@@ -21,7 +22,7 @@ export const SettingsTab = () => {
21
22
 
22
23
  if ( type === 'section' ) {
23
24
  return (
24
- <AccordionSection key={ type + '.' + index } title={ value.label } defaultExpanded>
25
+ <Section title={ value.label } key={ type + '.' + index } defaultExpanded={ true }>
25
26
  { value.items?.map( ( item ) => {
26
27
  if ( item.type === 'control' ) {
27
28
  return <Control key={ item.value.bind } control={ item.value } />;
@@ -30,17 +31,16 @@ export const SettingsTab = () => {
30
31
  // TODO: Handle 2nd level sections
31
32
  return null;
32
33
  } ) }
33
- </AccordionSection>
34
+ </Section>
34
35
  );
35
36
  }
36
37
 
37
38
  return null;
38
39
  } ) }
39
- </Stack>
40
+ </SectionsList>
40
41
  );
41
42
  };
42
43
 
43
- // TODO: Create control wrapper by type for different layouts.
44
44
  const Control = ( { control }: { control: Control[ 'value' ] } ) => {
45
45
  if ( ! getControlByType( control.type as ControlType ) ) {
46
46
  return null;
@@ -1,14 +1,14 @@
1
1
  import * as React from 'react';
2
- import { __ } from '@wordpress/i18n';
2
+ import { ColorControl, ControlLabel } from '@elementor/editor-controls';
3
3
  import { Grid } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
4
6
  import { StylesField } from '../../../controls-registry/styles-field';
5
- import { ControlLabel } from '../../control-label';
6
- import { ColorControl } from '../../../controls';
7
7
 
8
8
  export const BackgroundColorField = () => {
9
9
  return (
10
10
  <StylesField bind="background-color">
11
- <Grid container spacing={ 1 } alignItems="center">
11
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
12
12
  <Grid item xs={ 6 }>
13
13
  <ControlLabel>{ __( 'Color', 'elementor' ) }</ControlLabel>
14
14
  </Grid>
@@ -1,15 +1,17 @@
1
1
  import * as React from 'react';
2
- import { __ } from '@wordpress/i18n';
2
+ import { BackgroundOverlayRepeaterControl } from '@elementor/editor-controls';
3
3
  import { Stack } from '@elementor/ui';
4
- import { AccordionSection } from '../../accordion-section';
4
+
5
+ import { StylesField } from '../../../controls-registry/styles-field';
5
6
  import { BackgroundColorField } from './background-color-field';
6
7
 
7
8
  export const BackgroundSection = () => {
8
9
  return (
9
- <AccordionSection title={ __( 'Background', 'elementor' ) }>
10
- <Stack gap={ 1.5 }>
11
- <BackgroundColorField />
12
- </Stack>
13
- </AccordionSection>
10
+ <Stack gap={ 1.5 }>
11
+ <StylesField bind="background-image">
12
+ <BackgroundOverlayRepeaterControl />
13
+ </StylesField>
14
+ <BackgroundColorField />
15
+ </Stack>
14
16
  );
15
17
  };
@@ -1,14 +1,14 @@
1
1
  import * as React from 'react';
2
- import { __ } from '@wordpress/i18n';
2
+ import { ColorControl, ControlLabel } from '@elementor/editor-controls';
3
3
  import { Grid } from '@elementor/ui';
4
- import { ColorControl } from '../../../controls';
4
+ import { __ } from '@wordpress/i18n';
5
+
5
6
  import { StylesField } from '../../../controls-registry/styles-field';
6
- import { ControlLabel } from '../../control-label';
7
7
 
8
8
  export const BorderColorField = () => {
9
9
  return (
10
10
  <StylesField bind={ 'border-color' }>
11
- <Grid container spacing={ 1 } alignItems="center">
11
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
12
12
  <Grid item xs={ 6 }>
13
13
  <ControlLabel>{ __( 'Border Color', 'elementor' ) }</ControlLabel>
14
14
  </Grid>
@@ -1,10 +1,11 @@
1
1
  import * as React from 'react';
2
2
  import { __ } from '@wordpress/i18n';
3
- import { BorderWidthField } from './border-width-field';
3
+
4
+ import { useStylesField } from '../../../hooks/use-styles-field';
5
+ import { AddOrRemoveContent } from '../../add-or-remove-content';
4
6
  import { BorderColorField } from './border-color-field';
5
7
  import { BorderStyleField } from './border-style-field';
6
- import { AddOrRemoveContent } from '../../add-or-remove-content';
7
- import { useStylesField } from '../../../hooks/use-styles-field';
8
+ import { BorderWidthField } from './border-width-field';
8
9
 
9
10
  const initialSize = { $$type: 'size', value: { size: 1, unit: 'px' } };
10
11
  const initialBorderWidth = {
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
- import { __ } from '@wordpress/i18n';
2
+ import { type EqualUnequalItems, EqualUnequalSizesControl } from '@elementor/editor-controls';
3
+ import type { BorderRadiusPropValue } from '@elementor/editor-props';
3
4
  import {
4
5
  BorderCornersIcon,
5
6
  RadiusBottomLeftIcon,
@@ -7,8 +8,8 @@ import {
7
8
  RadiusTopLeftIcon,
8
9
  RadiusTopRightIcon,
9
10
  } from '@elementor/icons';
10
- import type { BorderRadiusPropValue } from '@elementor/editor-props';
11
- import { EqualUnequalItems, EqualUnequalSizesControl } from '../../../controls/controls/equal-unequal-sizes-control';
11
+ import { __ } from '@wordpress/i18n';
12
+
12
13
  import { StylesField } from '../../../controls-registry/styles-field';
13
14
 
14
15
  const corners: EqualUnequalItems< BorderRadiusPropValue[ '$$type' ], BorderRadiusPropValue > = [
@@ -1,16 +1,13 @@
1
1
  import * as React from 'react';
2
- import { AccordionSection } from '../../accordion-section';
3
2
  import { Divider, Stack } from '@elementor/ui';
4
- import { __ } from '@wordpress/i18n';
5
- import { BorderRadiusField } from './border-radius-field';
3
+
6
4
  import { BorderField } from './border-field';
5
+ import { BorderRadiusField } from './border-radius-field';
7
6
 
8
7
  export const BorderSection = () => (
9
- <AccordionSection title={ __( 'Border', 'elementor' ) }>
10
- <Stack gap={ 1.5 }>
11
- <BorderRadiusField />
12
- <Divider />
13
- <BorderField />
14
- </Stack>
15
- </AccordionSection>
8
+ <Stack gap={ 1.5 }>
9
+ <BorderRadiusField />
10
+ <Divider />
11
+ <BorderField />
12
+ </Stack>
16
13
  );
@@ -1,9 +1,9 @@
1
1
  import * as React from 'react';
2
- import { __ } from '@wordpress/i18n';
3
- import { SelectControl } from '../../../controls';
2
+ import { ControlLabel, SelectControl } from '@elementor/editor-controls';
4
3
  import { Grid } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
5
6
  import { StylesField } from '../../../controls-registry/styles-field';
6
- import { ControlLabel } from '../../control-label';
7
7
 
8
8
  const borderStyles = [
9
9
  { value: 'none', label: __( 'None', 'elementor' ) },
@@ -19,7 +19,7 @@ const borderStyles = [
19
19
  export const BorderStyleField = () => {
20
20
  return (
21
21
  <StylesField bind={ 'border-style' }>
22
- <Grid container spacing={ 1 } alignItems="center">
22
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
23
23
  <Grid item xs={ 6 }>
24
24
  <ControlLabel>{ __( 'Border Type', 'elementor' ) }</ControlLabel>
25
25
  </Grid>
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
- import { SideBottomIcon, SideLeftIcon, SideRightIcon, SideTopIcon, SideAllIcon } from '@elementor/icons';
2
+ import { type EqualUnequalItems, EqualUnequalSizesControl } from '@elementor/editor-controls';
3
+ import { type BorderWidthPropValue } from '@elementor/editor-props';
4
+ import { SideAllIcon, SideBottomIcon, SideLeftIcon, SideRightIcon, SideTopIcon } from '@elementor/icons';
3
5
  import { __ } from '@wordpress/i18n';
4
- import { BorderWidthPropValue } from '@elementor/editor-props';
5
- import { EqualUnequalItems, EqualUnequalSizesControl } from '../../../controls/controls/equal-unequal-sizes-control';
6
+
6
7
  import { StylesField } from '../../../controls-registry/styles-field';
7
8
 
8
9
  const edges: EqualUnequalItems< BorderWidthPropValue[ '$$type' ], BorderWidthPropValue > = [
@@ -1,18 +1,15 @@
1
1
  import * as React from 'react';
2
- import { __ } from '@wordpress/i18n';
2
+ import { BoxShadowRepeaterControl } from '@elementor/editor-controls';
3
3
  import { Stack } from '@elementor/ui';
4
- import { AccordionSection } from '../../accordion-section';
4
+
5
5
  import { StylesField } from '../../../controls-registry/styles-field';
6
- import { BoxShadowRepeaterControl } from '../../../controls';
7
6
 
8
7
  export const EffectsSection = () => {
9
8
  return (
10
- <AccordionSection title={ __( 'Effects', 'elementor' ) }>
11
- <Stack gap={ 1.5 }>
12
- <StylesField bind="box-shadow">
13
- <BoxShadowRepeaterControl />
14
- </StylesField>
15
- </Stack>
16
- </AccordionSection>
9
+ <Stack gap={ 1.5 }>
10
+ <StylesField bind="box-shadow">
11
+ <BoxShadowRepeaterControl />
12
+ </StylesField>
13
+ </Stack>
17
14
  );
18
15
  };
@@ -0,0 +1,92 @@
1
+ import * as React from 'react';
2
+ import type { JSX } from 'react';
3
+ import { ControlLabel, type ToggleButtonGroupItem, ToggleControl } from '@elementor/editor-controls';
4
+ import {
5
+ LayoutAlignCenterIcon as CenterIcon,
6
+ LayoutAlignLeftIcon,
7
+ LayoutAlignRightIcon,
8
+ LayoutDistributeVerticalIcon as JustifyIcon,
9
+ } from '@elementor/icons';
10
+ import { DirectionProvider, Grid, ThemeProvider, type ToggleButtonProps, useTheme, withDirection } from '@elementor/ui';
11
+ import { __ } from '@wordpress/i18n';
12
+
13
+ import { StylesField } from '../../../controls-registry/styles-field';
14
+ import { useDirection } from '../../../hooks/use-direction';
15
+ import { useStylesField } from '../../../hooks/use-styles-field';
16
+ import { type FlexDirection } from './flex-direction-field';
17
+
18
+ type AlignItems = 'start' | 'center' | 'end' | 'justify';
19
+
20
+ export const AlignItemsField = () => {
21
+ const options = useOptions(),
22
+ { isSiteRtl } = useDirection();
23
+
24
+ return (
25
+ <DirectionProvider rtl={ isSiteRtl }>
26
+ <ThemeProvider>
27
+ <StylesField bind="align-items">
28
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
29
+ <Grid item xs={ 6 }>
30
+ <ControlLabel>{ __( 'Align Items', 'elementor' ) }</ControlLabel>
31
+ </Grid>
32
+ <Grid item xs={ 6 } sx={ { display: 'flex', justifyContent: 'end' } }>
33
+ <ToggleControl options={ options } />
34
+ </Grid>
35
+ </Grid>
36
+ </StylesField>
37
+ </ThemeProvider>
38
+ </DirectionProvider>
39
+ );
40
+ };
41
+
42
+ const useOptions = (): ToggleButtonGroupItem< AlignItems >[] => {
43
+ const StartIcon = withDirection( LayoutAlignLeftIcon ),
44
+ EndIcon = withDirection( LayoutAlignRightIcon );
45
+
46
+ return [
47
+ {
48
+ value: 'start',
49
+ label: __( 'Start', 'elementor' ),
50
+ renderContent: ( { size } ) => <RotatedIcon icon={ StartIcon } size={ size } />,
51
+ showTooltip: true,
52
+ },
53
+ {
54
+ value: 'center',
55
+ label: __( 'Center', 'elementor' ),
56
+ renderContent: ( { size } ) => <RotatedIcon icon={ CenterIcon } size={ size } />,
57
+ showTooltip: true,
58
+ },
59
+ {
60
+ value: 'end',
61
+ label: __( 'End', 'elementor' ),
62
+ renderContent: ( { size } ) => <RotatedIcon icon={ EndIcon } size={ size } />,
63
+ showTooltip: true,
64
+ },
65
+ {
66
+ value: 'justify',
67
+ label: __( 'Justify', 'elementor' ),
68
+ renderContent: ( { size } ) => <RotatedIcon icon={ JustifyIcon } size={ size } />,
69
+ showTooltip: true,
70
+ },
71
+ ];
72
+ };
73
+
74
+ const RotatedIcon = ( { icon: Icon, size }: { icon: JSX.ElementType; size: ToggleButtonProps[ 'size' ] } ) => {
75
+ const [ direction ] = useStylesField< FlexDirection >( 'flex-direction' ),
76
+ isRtl = 'rtl' === useTheme().direction,
77
+ rotationMultiplier = isRtl ? -1 : 1;
78
+
79
+ const rotationAngelMap: Record< FlexDirection, number > = {
80
+ row: 90,
81
+ column: 0,
82
+ 'row-reverse': -90,
83
+ 'column-reverse': -180,
84
+ };
85
+
86
+ return (
87
+ <Icon
88
+ fontSize={ size }
89
+ sx={ { transition: '.3s', rotate: `${ rotationAngelMap[ direction || 'row' ] * rotationMultiplier }deg` } }
90
+ />
91
+ );
92
+ };
@@ -0,0 +1,32 @@
1
+ import * as React from 'react';
2
+ import { ControlLabel, type ToggleButtonGroupItem, ToggleControl } from '@elementor/editor-controls';
3
+ import { Stack } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ import { StylesField } from '../../../controls-registry/styles-field';
7
+
8
+ type Displays = 'block' | 'flex';
9
+
10
+ export const DisplayField = () => {
11
+ const options: ToggleButtonGroupItem< Displays >[] = [
12
+ {
13
+ value: 'block',
14
+ renderContent: () => __( 'Block', 'elementor' ),
15
+ label: __( 'Block', 'elementor' ),
16
+ },
17
+ {
18
+ value: 'flex',
19
+ renderContent: () => __( 'Flex', 'elementor' ),
20
+ label: __( 'Flex', 'elementor' ),
21
+ },
22
+ ];
23
+
24
+ return (
25
+ <StylesField bind="display">
26
+ <Stack gap={ 1 }>
27
+ <ControlLabel>{ __( 'Display', 'elementor' ) }</ControlLabel>
28
+ <ToggleControl options={ options } fullWidth={ true } />
29
+ </Stack>
30
+ </StylesField>
31
+ );
32
+ };
@@ -0,0 +1,64 @@
1
+ import * as React from 'react';
2
+ import { ControlLabel, type ToggleButtonGroupItem, ToggleControl } from '@elementor/editor-controls';
3
+ import { ArrowDownSmallIcon, ArrowLeftIcon, ArrowRightIcon, ArrowUpSmallIcon } from '@elementor/icons';
4
+ import { DirectionProvider, Grid, ThemeProvider, withDirection } from '@elementor/ui';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ import { StylesField } from '../../../controls-registry/styles-field';
8
+ import { useDirection } from '../../../hooks/use-direction';
9
+
10
+ export type FlexDirection = 'row' | 'row-reverse' | 'column' | 'column-reverse';
11
+
12
+ const options: ToggleButtonGroupItem< FlexDirection >[] = [
13
+ {
14
+ value: 'row',
15
+ label: __( 'Row', 'elementor' ),
16
+ renderContent: ( { size } ) => {
17
+ const StartIcon = withDirection( ArrowRightIcon );
18
+ return <StartIcon fontSize={ size } />;
19
+ },
20
+ showTooltip: true,
21
+ },
22
+ {
23
+ value: 'column',
24
+ label: __( 'Column', 'elementor' ),
25
+ renderContent: ( { size } ) => <ArrowDownSmallIcon fontSize={ size } />,
26
+ showTooltip: true,
27
+ },
28
+ {
29
+ value: 'row-reverse',
30
+ label: __( 'Row reverse', 'elementor' ),
31
+ renderContent: ( { size } ) => {
32
+ const EndIcon = withDirection( ArrowLeftIcon );
33
+ return <EndIcon fontSize={ size } />;
34
+ },
35
+ showTooltip: true,
36
+ },
37
+ {
38
+ value: 'column-reverse',
39
+ label: __( 'Column reverse', 'elementor' ),
40
+ renderContent: ( { size } ) => <ArrowUpSmallIcon fontSize={ size } />,
41
+ showTooltip: true,
42
+ },
43
+ ];
44
+
45
+ export const FlexDirectionField = () => {
46
+ const { isSiteRtl } = useDirection();
47
+
48
+ return (
49
+ <DirectionProvider rtl={ isSiteRtl }>
50
+ <ThemeProvider>
51
+ <StylesField bind="flex-direction">
52
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
53
+ <Grid item xs={ 6 }>
54
+ <ControlLabel>{ __( 'Direction', 'elementor' ) }</ControlLabel>
55
+ </Grid>
56
+ <Grid item xs={ 6 } sx={ { display: 'flex', justifyContent: 'end' } }>
57
+ <ToggleControl options={ options } />
58
+ </Grid>
59
+ </Grid>
60
+ </StylesField>
61
+ </ThemeProvider>
62
+ </DirectionProvider>
63
+ );
64
+ };
@@ -0,0 +1,114 @@
1
+ import * as React from 'react';
2
+ import { useState } from 'react';
3
+ import {
4
+ ControlLabel,
5
+ ControlToggleButtonGroup,
6
+ NumberControl,
7
+ type ToggleButtonGroupItem,
8
+ } from '@elementor/editor-controls';
9
+ import { ArrowDownSmallIcon, ArrowUpSmallIcon, PencilIcon } from '@elementor/icons';
10
+ import { DirectionProvider, Grid, Stack, ThemeProvider } from '@elementor/ui';
11
+ import { __ } from '@wordpress/i18n';
12
+
13
+ import { StylesField } from '../../../controls-registry/styles-field';
14
+ import { useDirection } from '../../../hooks/use-direction';
15
+ import { useStylesField } from '../../../hooks/use-styles-field';
16
+
17
+ type GroupControlItemOption = 'first' | 'last' | 'custom';
18
+
19
+ export const FIRST_DEFAULT_VALUE = -99999,
20
+ LAST_DEFAULT_VALUE = 99999,
21
+ FIRST = 'first',
22
+ LAST = 'last',
23
+ CUSTOM = 'custom';
24
+
25
+ const items: ToggleButtonGroupItem< GroupControlItemOption >[] = [
26
+ {
27
+ value: FIRST,
28
+ label: __( 'First', 'elementor' ),
29
+ renderContent: ( { size } ) => <ArrowUpSmallIcon fontSize={ size } />,
30
+ showTooltip: true,
31
+ },
32
+ {
33
+ value: LAST,
34
+ label: __( 'Last', 'elementor' ),
35
+ renderContent: ( { size } ) => <ArrowDownSmallIcon fontSize={ size } />,
36
+ showTooltip: true,
37
+ },
38
+ {
39
+ value: CUSTOM,
40
+ label: __( 'Custom', 'elementor' ),
41
+ renderContent: ( { size } ) => <PencilIcon fontSize={ size } />,
42
+ showTooltip: true,
43
+ },
44
+ ];
45
+
46
+ export const FlexOrderField = () => {
47
+ const { isSiteRtl } = useDirection(),
48
+ [ order, setOrder ] = useStylesField< number | null >( 'order' );
49
+
50
+ const [ groupControlValue, setGroupControlValue ] = useState( getGroupControlValue( order ) );
51
+
52
+ const handleToggleButtonChange = ( group: GroupControlItemOption | null ) => {
53
+ setGroupControlValue( group );
54
+
55
+ const orderValueMap = {
56
+ [ FIRST ]: FIRST_DEFAULT_VALUE,
57
+ [ LAST ]: LAST_DEFAULT_VALUE,
58
+ [ CUSTOM ]: null,
59
+ };
60
+
61
+ setOrder( group ? orderValueMap[ group ] : null );
62
+ };
63
+
64
+ return (
65
+ <DirectionProvider rtl={ isSiteRtl }>
66
+ <ThemeProvider>
67
+ <Stack gap={ 2 }>
68
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
69
+ <Grid item xs={ 6 }>
70
+ <ControlLabel>{ __( 'Order', 'elementor' ) }</ControlLabel>
71
+ </Grid>
72
+ <Grid item xs={ 6 } sx={ { display: 'flex', justifyContent: 'end' } }>
73
+ <ControlToggleButtonGroup
74
+ items={ items }
75
+ value={ groupControlValue }
76
+ onChange={ handleToggleButtonChange }
77
+ exclusive={ true }
78
+ />
79
+ </Grid>
80
+ </Grid>
81
+
82
+ { CUSTOM === groupControlValue && (
83
+ <StylesField bind={ 'order' }>
84
+ <Grid container gap={ 2 } alignItems="center" flexWrap="nowrap">
85
+ <Grid item xs={ 6 }>
86
+ <ControlLabel>{ __( 'Custom order', 'elementor' ) }</ControlLabel>
87
+ </Grid>
88
+ <Grid item xs={ 6 } sx={ { display: 'flex', justifyContent: 'end' } }>
89
+ <NumberControl
90
+ min={ FIRST_DEFAULT_VALUE + 1 }
91
+ max={ LAST_DEFAULT_VALUE - 1 }
92
+ shouldForceInt={ true }
93
+ />
94
+ </Grid>
95
+ </Grid>
96
+ </StylesField>
97
+ ) }
98
+ </Stack>
99
+ </ThemeProvider>
100
+ </DirectionProvider>
101
+ );
102
+ };
103
+
104
+ const getGroupControlValue = ( order: number | null ): GroupControlItemOption | null => {
105
+ if ( LAST_DEFAULT_VALUE === order ) {
106
+ return LAST;
107
+ }
108
+
109
+ if ( FIRST_DEFAULT_VALUE === order ) {
110
+ return FIRST;
111
+ }
112
+
113
+ return 0 === order || order ? CUSTOM : null;
114
+ };
@@ -0,0 +1,109 @@
1
+ import * as React from 'react';
2
+ import { type JSX } from 'react';
3
+ import { ControlLabel, type ToggleButtonGroupItem, ToggleControl } from '@elementor/editor-controls';
4
+ import {
5
+ JustifyBottomIcon,
6
+ JustifyCenterIcon as CenterIcon,
7
+ JustifyDistributeVerticalIcon as EvenlyIcon,
8
+ JustifySpaceAroundVerticalIcon as AroundIcon,
9
+ JustifySpaceBetweenVerticalIcon as BetweenIcon,
10
+ JustifyTopIcon,
11
+ } from '@elementor/icons';
12
+ import {
13
+ DirectionProvider,
14
+ Stack,
15
+ ThemeProvider,
16
+ type ToggleButtonProps,
17
+ useTheme,
18
+ withDirection,
19
+ } from '@elementor/ui';
20
+ import { __ } from '@wordpress/i18n';
21
+
22
+ import { StylesField } from '../../../controls-registry/styles-field';
23
+ import { useDirection } from '../../../hooks/use-direction';
24
+ import { useStylesField } from '../../../hooks/use-styles-field';
25
+ import { type FlexDirection } from './flex-direction-field';
26
+
27
+ type JustifyContent = 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly';
28
+
29
+ export const JustifyContentField = () => {
30
+ const options = useOptions(),
31
+ { isSiteRtl } = useDirection();
32
+
33
+ return (
34
+ <DirectionProvider rtl={ isSiteRtl }>
35
+ <ThemeProvider>
36
+ <StylesField bind="justify-content">
37
+ <Stack gap={ 1 }>
38
+ <ControlLabel>{ __( 'Justify Content', 'elementor' ) }</ControlLabel>
39
+ <ToggleControl options={ options } fullWidth={ true } />
40
+ </Stack>
41
+ </StylesField>
42
+ </ThemeProvider>
43
+ </DirectionProvider>
44
+ );
45
+ };
46
+
47
+ const useOptions = (): ToggleButtonGroupItem< JustifyContent >[] => {
48
+ const StartIcon = withDirection( JustifyTopIcon ),
49
+ EndIcon = withDirection( JustifyBottomIcon );
50
+
51
+ return [
52
+ {
53
+ value: 'start',
54
+ label: __( 'Start', 'elementor' ),
55
+ renderContent: ( { size } ) => <RotatedIcon icon={ StartIcon } size={ size } />,
56
+ showTooltip: true,
57
+ },
58
+ {
59
+ value: 'center',
60
+ label: __( 'Center', 'elementor' ),
61
+ renderContent: ( { size } ) => <RotatedIcon icon={ CenterIcon } size={ size } />,
62
+ showTooltip: true,
63
+ },
64
+ {
65
+ value: 'end',
66
+ label: __( 'End', 'elementor' ),
67
+ renderContent: ( { size } ) => <RotatedIcon icon={ EndIcon } size={ size } />,
68
+ showTooltip: true,
69
+ },
70
+ {
71
+ value: 'space-between',
72
+ label: __( 'Space between', 'elementor' ),
73
+ renderContent: ( { size } ) => <RotatedIcon icon={ BetweenIcon } size={ size } />,
74
+ showTooltip: true,
75
+ },
76
+ {
77
+ value: 'space-around',
78
+ label: __( 'Space around', 'elementor' ),
79
+ renderContent: ( { size } ) => <RotatedIcon icon={ AroundIcon } size={ size } />,
80
+ showTooltip: true,
81
+ },
82
+ {
83
+ value: 'space-evenly',
84
+ label: __( 'Space evenly', 'elementor' ),
85
+ renderContent: ( { size } ) => <RotatedIcon icon={ EvenlyIcon } size={ size } />,
86
+ showTooltip: true,
87
+ },
88
+ ];
89
+ };
90
+
91
+ const RotatedIcon = ( { icon: Icon, size }: { icon: JSX.ElementType; size: ToggleButtonProps[ 'size' ] } ) => {
92
+ const [ direction ] = useStylesField< FlexDirection >( 'flex-direction' ),
93
+ isRtl = 'rtl' === useTheme().direction,
94
+ rotationMultiplier = isRtl ? -1 : 1;
95
+
96
+ const rotationAngelMap: Record< FlexDirection, number > = {
97
+ row: -90,
98
+ column: 0,
99
+ 'row-reverse': 90,
100
+ 'column-reverse': 180,
101
+ };
102
+
103
+ return (
104
+ <Icon
105
+ fontSize={ size }
106
+ sx={ { transition: '.3s', rotate: `${ rotationAngelMap[ direction || 'row' ] * rotationMultiplier }deg` } }
107
+ />
108
+ );
109
+ };