@elementor/editor-controls 0.1.1 → 0.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.
@@ -0,0 +1,90 @@
1
+ import * as React from 'react';
2
+ import { type LinkPropValue, type UrlPropValue } from '@elementor/editor-props';
3
+ import { MinusIcon, PlusIcon } from '@elementor/icons';
4
+ import { Collapse, Divider, Grid, IconButton, Stack, Switch } from '@elementor/ui';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ import { BoundPropProvider, useBoundProp } from '../bound-prop-context';
8
+ import { ControlLabel } from '../components/control-label';
9
+ import { createControl } from '../create-control';
10
+ import { UrlControl } from './url-control';
11
+
12
+ const SIZE = 'tiny';
13
+
14
+ const DEFAULT_LINK_CONTROL_VALUE: LinkPropValue = {
15
+ $$type: 'link',
16
+ value: {
17
+ enabled: false,
18
+ href: {
19
+ $$type: 'url',
20
+ value: '',
21
+ },
22
+ isTargetBlank: false,
23
+ },
24
+ };
25
+
26
+ export const LinkControl = createControl( () => {
27
+ const { value = DEFAULT_LINK_CONTROL_VALUE, setValue } = useBoundProp< LinkPropValue >();
28
+ const { enabled, href, isTargetBlank } = value?.value || {};
29
+
30
+ const handleOnChange = < T extends keyof LinkPropValue[ 'value' ] >(
31
+ key: T,
32
+ newValue: LinkPropValue[ 'value' ][ T ]
33
+ ) => {
34
+ setValue( {
35
+ $$type: 'link',
36
+ value: {
37
+ ...( value?.value ?? DEFAULT_LINK_CONTROL_VALUE.value ),
38
+ [ key ]: newValue,
39
+ },
40
+ } );
41
+ };
42
+
43
+ return (
44
+ <Stack gap={ 1.5 }>
45
+ <Divider />
46
+ <Stack
47
+ direction="row"
48
+ sx={ {
49
+ justifyContent: 'space-between',
50
+ alignItems: 'center',
51
+ } }
52
+ >
53
+ <ControlLabel>{ __( 'Link', 'elementor' ) }</ControlLabel>
54
+ <IconButton size={ SIZE } onClick={ () => handleOnChange( 'enabled', ! enabled ) }>
55
+ { enabled ? <MinusIcon fontSize={ SIZE } /> : <PlusIcon fontSize={ SIZE } /> }
56
+ </IconButton>
57
+ </Stack>
58
+ <Collapse in={ enabled } timeout="auto" unmountOnExit>
59
+ <Stack gap={ 1.5 }>
60
+ <BoundPropProvider
61
+ value={ href }
62
+ setValue={ ( newHref ) => handleOnChange( 'href', newHref as UrlPropValue ) }
63
+ bind={ 'href' }
64
+ >
65
+ <UrlControl placeholder={ __( 'Paste URL or type', 'elementor' ) } />
66
+ </BoundPropProvider>
67
+
68
+ <SwitchControl
69
+ value={ isTargetBlank }
70
+ onSwitch={ () => handleOnChange( 'isTargetBlank', ! isTargetBlank ) }
71
+ />
72
+ </Stack>
73
+ </Collapse>
74
+ </Stack>
75
+ );
76
+ } );
77
+
78
+ // @TODO Should be refactored in ED-16323
79
+ const SwitchControl = ( { value, onSwitch }: { value: boolean; onSwitch: () => void } ) => {
80
+ return (
81
+ <Grid container alignItems="center" flexWrap="nowrap" justifyContent="space-between">
82
+ <Grid item>
83
+ <ControlLabel>{ __( 'Open in new tab', 'elementor' ) }</ControlLabel>
84
+ </Grid>
85
+ <Grid item>
86
+ <Switch checked={ value } onChange={ onSwitch } />
87
+ </Grid>
88
+ </Grid>
89
+ );
90
+ };
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type LinkedDimensionsPropValue, type PropValue } from '@elementor/editor-props';
2
+ import { linkedDimensionsPropTypeUtil, type PropValue } from '@elementor/editor-props';
3
3
  import { DetachIcon, LinkIcon, SideBottomIcon, SideLeftIcon, SideRightIcon, SideTopIcon } from '@elementor/icons';
4
4
  import { Grid, Stack, ToggleButton } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
@@ -12,8 +12,8 @@ import { SizeControl } from './size-control';
12
12
  export type Position = 'top' | 'right' | 'bottom' | 'left';
13
13
 
14
14
  export const LinkedDimensionsControl = createControl( ( { label }: { label: string } ) => {
15
- const { value, setValue } = useBoundProp< LinkedDimensionsPropValue >();
16
- const { top, right, bottom, left, isLinked = true } = value?.value || {};
15
+ const { value, setValue } = useBoundProp( linkedDimensionsPropTypeUtil );
16
+ const { top, right, bottom, left, isLinked = true } = value || {};
17
17
 
18
18
  const setLinkedValue = ( position: Position, newValue: PropValue ) => {
19
19
  const updatedValue = {
@@ -25,10 +25,7 @@ export const LinkedDimensionsControl = createControl( ( { label }: { label: stri
25
25
  [ position ]: newValue,
26
26
  };
27
27
 
28
- setValue( {
29
- $$type: 'linked-dimensions',
30
- value: updatedValue,
31
- } );
28
+ setValue( updatedValue );
32
29
  };
33
30
 
34
31
  const toggleLinked = () => {
@@ -40,10 +37,7 @@ export const LinkedDimensionsControl = createControl( ( { label }: { label: stri
40
37
  left: ! isLinked ? top : left,
41
38
  };
42
39
 
43
- setValue( {
44
- $$type: 'linked-dimensions',
45
- value: updatedValue,
46
- } );
40
+ setValue( updatedValue );
47
41
  };
48
42
 
49
43
  const LinkedIcon = isLinked ? LinkIcon : DetachIcon;
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { numberPropTypeUtil } from '@elementor/editor-props';
2
3
  import { TextField } from '@elementor/ui';
3
4
 
4
5
  import { useBoundProp } from '../bound-prop-context';
@@ -6,7 +7,7 @@ import ControlActions from '../control-actions/control-actions';
6
7
  import { createControl } from '../create-control';
7
8
 
8
9
  const isEmptyOrNaN = ( value?: string | number ) =>
9
- value === undefined || value === '' || Number.isNaN( Number( value ) );
10
+ value === null || value === undefined || value === '' || Number.isNaN( Number( value ) );
10
11
 
11
12
  export const NumberControl = createControl(
12
13
  ( {
@@ -22,18 +23,18 @@ export const NumberControl = createControl(
22
23
  step?: number;
23
24
  shouldForceInt?: boolean;
24
25
  } ) => {
25
- const { value, setValue } = useBoundProp< number | undefined >();
26
+ const { value, setValue } = useBoundProp( numberPropTypeUtil );
26
27
 
27
28
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
28
29
  const eventValue: string = event.target.value;
29
30
 
30
31
  if ( isEmptyOrNaN( eventValue ) ) {
31
- setValue( undefined );
32
+ setValue( null );
32
33
 
33
34
  return;
34
35
  }
35
36
 
36
- const formattedValue = shouldForceInt ? +parseFloat( eventValue ) : Number( eventValue );
37
+ const formattedValue = shouldForceInt ? +parseInt( eventValue ) : Number( eventValue );
37
38
 
38
39
  setValue( Math.min( Math.max( formattedValue, min ), max ) );
39
40
  };
@@ -1,20 +1,20 @@
1
1
  import * as React from 'react';
2
- import { type PropValue } from '@elementor/editor-props';
2
+ import { stringPropTypeUtil, type StringPropValue } from '@elementor/editor-props';
3
3
  import { MenuItem, Select, type SelectChangeEvent } from '@elementor/ui';
4
4
 
5
5
  import { useBoundProp } from '../bound-prop-context';
6
6
  import ControlActions from '../control-actions/control-actions';
7
7
  import { createControl } from '../create-control';
8
8
 
9
- type Props< T > = {
10
- options: Array< { label: string; value: T; disabled?: boolean } >;
9
+ type Props = {
10
+ options: Array< { label: string; value: StringPropValue[ 'value' ]; disabled?: boolean } >;
11
11
  };
12
12
 
13
- export const SelectControl = createControl( < T extends PropValue >( { options }: Props< T > ) => {
14
- const { value, setValue } = useBoundProp< T >();
13
+ export const SelectControl = createControl( ( { options }: Props ) => {
14
+ const { value, setValue } = useBoundProp( stringPropTypeUtil );
15
15
 
16
- const handleChange = ( event: SelectChangeEvent< T > ) => {
17
- setValue( event.target.value as T );
16
+ const handleChange = ( event: SelectChangeEvent< StringPropValue[ 'value' ] > ) => {
17
+ setValue( event.target.value );
18
18
  };
19
19
 
20
20
  return (
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type SizePropValue } from '@elementor/editor-props';
2
+ import { sizePropTypeUtil } from '@elementor/editor-props';
3
3
  import { InputAdornment } from '@elementor/ui';
4
4
 
5
5
  import { useBoundProp } from '../bound-prop-context';
@@ -22,25 +22,19 @@ export type SizeControlProps = {
22
22
  };
23
23
 
24
24
  export const SizeControl = createControl( ( { units = defaultUnits, placeholder, startIcon }: SizeControlProps ) => {
25
- const { value, setValue } = useBoundProp< SizePropValue | undefined >();
25
+ const { value, setValue } = useBoundProp( sizePropTypeUtil );
26
26
 
27
- const [ state, setState ] = useSyncExternalState< SizePropValue >( {
27
+ const [ state, setState ] = useSyncExternalState( {
28
28
  external: value,
29
29
  setExternal: setValue,
30
- persistWhen: ( controlValue ) => !! controlValue?.value?.size || controlValue?.value?.size === 0,
31
- fallback: ( controlValue ) => ( {
32
- $$type: 'size',
33
- value: { unit: controlValue?.value?.unit || defaultUnit, size: defaultSize },
34
- } ),
30
+ persistWhen: ( controlValue ) => !! controlValue?.size || controlValue?.size === 0,
31
+ fallback: ( controlValue ) => ( { unit: controlValue?.unit || defaultUnit, size: defaultSize } ),
35
32
  } );
36
33
 
37
34
  const handleUnitChange = ( unit: Unit ) => {
38
35
  setState( ( prev ) => ( {
39
- ...prev,
40
- value: {
41
- ...prev.value,
42
- unit,
43
- },
36
+ size: prev?.size ?? defaultSize,
37
+ unit,
44
38
  } ) );
45
39
  };
46
40
 
@@ -49,10 +43,7 @@ export const SizeControl = createControl( ( { units = defaultUnits, placeholder,
49
43
 
50
44
  setState( ( prev ) => ( {
51
45
  ...prev,
52
- value: {
53
- ...prev.value,
54
- size: size || size === '0' ? parseFloat( size ) : defaultSize,
55
- },
46
+ size: size || size === '0' ? parseFloat( size ) : defaultSize,
56
47
  } ) );
57
48
  };
58
49
 
@@ -63,13 +54,13 @@ export const SizeControl = createControl( ( { units = defaultUnits, placeholder,
63
54
  <SelectionEndAdornment
64
55
  options={ units }
65
56
  onClick={ handleUnitChange }
66
- value={ state.value.unit ?? defaultUnit }
57
+ value={ state?.unit ?? defaultUnit }
67
58
  />
68
59
  }
69
60
  placeholder={ placeholder }
70
61
  startAdornment={ startIcon ?? <InputAdornment position="start">{ startIcon }</InputAdornment> }
71
62
  type="number"
72
- value={ Number.isNaN( state.value.size ) ? '' : state.value.size }
63
+ value={ Number.isNaN( state?.size ) ? '' : state?.size }
73
64
  onChange={ handleSizeChange }
74
65
  />
75
66
  </ControlActions>
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type ColorPropValue, type PropValue, type SizePropValue, type StrokePropValue } from '@elementor/editor-props';
2
+ import { type ColorPropValue, type PropValue, type SizePropValue, strokePropTypeUtil } from '@elementor/editor-props';
3
3
  import { Grid, Stack } from '@elementor/ui';
4
4
  import { __ } from '@wordpress/i18n';
5
5
 
@@ -18,34 +18,27 @@ export type StrokeProps< T > = {
18
18
  label: string;
19
19
  children: React.ReactNode;
20
20
  };
21
-
22
21
  const units: Unit[] = [ 'px', 'em', 'rem' ];
23
22
 
24
23
  export const StrokeControl = createControl( () => {
25
- const { value, setValue } = useBoundProp< StrokePropValue >();
24
+ const { value, setValue } = useBoundProp( strokePropTypeUtil );
26
25
 
27
26
  const setStrokeWidth = ( newValue: SizePropValue ) => {
28
27
  const updatedValue = {
29
- ...value?.value,
28
+ ...value,
30
29
  width: newValue,
31
30
  };
32
31
 
33
- setValue( {
34
- $$type: 'stroke',
35
- value: updatedValue,
36
- } );
32
+ setValue( updatedValue );
37
33
  };
38
34
 
39
35
  const setStrokeColor = ( newValue: ColorPropValue ) => {
40
36
  const updatedValue = {
41
- ...value?.value,
37
+ ...value,
42
38
  color: newValue,
43
39
  };
44
40
 
45
- setValue( {
46
- $$type: 'stroke',
47
- value: updatedValue,
48
- } );
41
+ setValue( updatedValue );
49
42
  };
50
43
 
51
44
  return (
@@ -53,7 +46,7 @@ export const StrokeControl = createControl( () => {
53
46
  <Control
54
47
  bind="width"
55
48
  label={ __( 'Stroke Width', 'elementor' ) }
56
- value={ value?.value.width }
49
+ value={ value?.width }
57
50
  setValue={ setStrokeWidth }
58
51
  >
59
52
  <SizeControl units={ units } />
@@ -62,7 +55,7 @@ export const StrokeControl = createControl( () => {
62
55
  <Control
63
56
  bind="color"
64
57
  label={ __( 'Stroke Color', 'elementor' ) }
65
- value={ value?.value.color }
58
+ value={ value?.color }
66
59
  setValue={ setStrokeColor }
67
60
  >
68
61
  <ColorControl />
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { stringPropTypeUtil } from '@elementor/editor-props';
2
3
  import { TextField } from '@elementor/ui';
3
4
 
4
5
  import { useBoundProp } from '../bound-prop-context';
@@ -10,7 +11,7 @@ type Props = {
10
11
  };
11
12
 
12
13
  export const TextAreaControl = createControl( ( { placeholder }: Props ) => {
13
- const { value, setValue } = useBoundProp< string >();
14
+ const { value, setValue } = useBoundProp( stringPropTypeUtil );
14
15
 
15
16
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
16
17
  setValue( event.target.value );
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import { stringPropTypeUtil } from '@elementor/editor-props';
2
3
  import { TextField } from '@elementor/ui';
3
4
 
4
5
  import { useBoundProp } from '../bound-prop-context';
@@ -6,7 +7,7 @@ import ControlActions from '../control-actions/control-actions';
6
7
  import { createControl } from '../create-control';
7
8
 
8
9
  export const TextControl = createControl( ( { placeholder }: { placeholder?: string } ) => {
9
- const { value, setValue } = useBoundProp< string >( '' );
10
+ const { value, setValue } = useBoundProp( stringPropTypeUtil );
10
11
 
11
12
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
12
13
 
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type PropValue } from '@elementor/editor-props';
2
+ import { type PropValue, stringPropTypeUtil, type StringPropValue } from '@elementor/editor-props';
3
3
  import { type ToggleButtonProps } from '@elementor/ui';
4
4
 
5
5
  import { useBoundProp } from '../bound-prop-context';
@@ -13,17 +13,17 @@ type ToggleControlProps< T extends PropValue > = {
13
13
  };
14
14
 
15
15
  export const ToggleControl = createControl(
16
- < T extends PropValue >( { options, fullWidth = false, size = 'tiny' }: ToggleControlProps< T > ) => {
17
- const { value, setValue } = useBoundProp< T | null >();
16
+ ( { options, fullWidth = false, size = 'tiny' }: ToggleControlProps< StringPropValue[ 'value' ] > ) => {
17
+ const { value, setValue } = useBoundProp( stringPropTypeUtil );
18
18
 
19
- const handleToggle = ( option: T | null ) => {
19
+ const handleToggle = ( option: StringPropValue[ 'value' ] | null ) => {
20
20
  setValue( option );
21
21
  };
22
22
 
23
23
  return (
24
24
  <ControlToggleButtonGroup
25
25
  items={ options }
26
- value={ value || null }
26
+ value={ value ?? null }
27
27
  onChange={ handleToggle }
28
28
  exclusive={ true }
29
29
  fullWidth={ fullWidth }
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ import { type UrlPropValue } from '@elementor/editor-props';
3
+ import { TextField } from '@elementor/ui';
4
+
5
+ import { useBoundProp } from '../bound-prop-context';
6
+ import ControlActions from '../control-actions/control-actions';
7
+ import { createControl } from '../create-control';
8
+
9
+ export const UrlControl = createControl( ( { placeholder }: { placeholder?: string } ) => {
10
+ const { value, setValue } = useBoundProp< UrlPropValue >();
11
+
12
+ const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) =>
13
+ setValue( {
14
+ $$type: 'url',
15
+ value: event.target.value,
16
+ } );
17
+
18
+ return (
19
+ <ControlActions>
20
+ <TextField
21
+ size="tiny"
22
+ fullWidth
23
+ value={ value?.value }
24
+ onChange={ handleChange }
25
+ placeholder={ placeholder }
26
+ />
27
+ </ControlActions>
28
+ );
29
+ } );
@@ -1,10 +1,10 @@
1
1
  import { useEffect, useState } from 'react';
2
2
 
3
3
  type UseInternalStateOptions< TValue > = {
4
- external: TValue | undefined;
5
- setExternal: ( value: TValue | undefined ) => void;
6
- persistWhen: ( value: TValue | undefined ) => boolean;
7
- fallback: ( value: TValue | undefined ) => TValue;
4
+ external: TValue | null;
5
+ setExternal: ( value: TValue | null ) => void;
6
+ persistWhen: ( value: TValue | null ) => boolean;
7
+ fallback: ( value: TValue | null ) => TValue;
8
8
  };
9
9
 
10
10
  export const useSyncExternalState = < TValue, >( {
@@ -13,15 +13,15 @@ export const useSyncExternalState = < TValue, >( {
13
13
  persistWhen,
14
14
  fallback,
15
15
  }: UseInternalStateOptions< TValue > ) => {
16
- function toExternal( internalValue: TValue | undefined ) {
16
+ function toExternal( internalValue: TValue | null ) {
17
17
  if ( persistWhen( internalValue ) ) {
18
18
  return internalValue;
19
19
  }
20
20
 
21
- return undefined;
21
+ return null;
22
22
  }
23
23
 
24
- function toInternal( externalValue: TValue | undefined, internalValue: TValue | undefined ) {
24
+ function toInternal( externalValue: TValue | null, internalValue: TValue | null ) {
25
25
  if ( ! externalValue ) {
26
26
  return fallback( internalValue );
27
27
  }
@@ -29,7 +29,7 @@ export const useSyncExternalState = < TValue, >( {
29
29
  return externalValue;
30
30
  }
31
31
 
32
- const [ internal, setInternal ] = useState< TValue >( toInternal( external, undefined ) );
32
+ const [ internal, setInternal ] = useState< TValue >( toInternal( external, null ) );
33
33
 
34
34
  useEffect( () => {
35
35
  setInternal( ( prevInternal ) => toInternal( external, prevInternal ) );
package/src/index.ts CHANGED
@@ -13,6 +13,9 @@ export { NumberControl } from './controls/number-control';
13
13
  export { EqualUnequalSizesControl } from './controls/equal-unequal-sizes-control';
14
14
  export { LinkedDimensionsControl } from './controls/linked-dimensions-control';
15
15
  export { FontFamilyControl } from './controls/font-family-control';
16
+ export { UrlControl } from './controls/url-control';
17
+ export { LinkControl } from './controls/link-control';
18
+ export { GapControl } from './controls/gap-control';
16
19
 
17
20
  // components
18
21
  export { ControlLabel } from './components/control-label';