@elementor/editor-controls 1.1.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 (31) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/index.d.mts +20 -11
  3. package/dist/index.d.ts +20 -11
  4. package/dist/index.js +739 -565
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +548 -372
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +8 -8
  9. package/src/components/font-family-selector.tsx +30 -13
  10. package/src/components/popover-content.tsx +3 -11
  11. package/src/components/repeater.tsx +3 -1
  12. package/src/components/text-field-popover.tsx +3 -3
  13. package/src/controls/aspect-ratio-control.tsx +20 -2
  14. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +2 -2
  15. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-size.tsx +2 -2
  16. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +9 -4
  17. package/src/controls/box-shadow-repeater-control.tsx +2 -2
  18. package/src/controls/equal-unequal-sizes-control.tsx +3 -9
  19. package/src/controls/filter-repeater-control.tsx +186 -0
  20. package/src/controls/font-family-control/font-family-control.tsx +6 -2
  21. package/src/controls/gap-control.tsx +3 -3
  22. package/src/controls/image-control.tsx +22 -35
  23. package/src/controls/key-value-control.tsx +39 -19
  24. package/src/controls/link-control.tsx +3 -1
  25. package/src/controls/linked-dimensions-control.tsx +3 -3
  26. package/src/controls/number-control.tsx +3 -3
  27. package/src/controls/repeatable-control.tsx +38 -8
  28. package/src/controls/size-control.tsx +3 -3
  29. package/src/controls/stroke-control.tsx +2 -2
  30. package/src/controls/svg-media-control.tsx +0 -2
  31. package/src/index.ts +3 -1
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { type ChangeEvent, useMemo, useState } from 'react';
3
3
  import { keyValuePropTypeUtil } from '@elementor/editor-props';
4
- import { FormHelperText, FormLabel, Grid, type SxProps, TextField, type Theme } from '@elementor/ui';
4
+ import { FormHelperText, FormLabel, Grid, TextField } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
6
6
 
7
7
  import { useBoundProp } from '../bound-prop-context';
@@ -13,7 +13,6 @@ type FieldType = 'key' | 'value';
13
13
  type KeyValueControlProps = {
14
14
  keyName?: string;
15
15
  valueName?: string;
16
- sx?: SxProps< Theme >;
17
16
  regexKey?: string;
18
17
  regexValue?: string;
19
18
  validationErrorMessage?: string;
@@ -23,12 +22,15 @@ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {}
23
22
  const { value, setValue } = useBoundProp( keyValuePropTypeUtil );
24
23
  const [ keyError, setKeyError ] = useState< string | null >( null );
25
24
  const [ valueError, setValueError ] = useState< string | null >( null );
25
+
26
+ const [ sessionState, setSessionState ] = useState( {
27
+ key: value?.key?.value || '',
28
+ value: value?.value?.value || '',
29
+ } );
30
+
26
31
  const keyLabel = props.keyName || __( 'Key', 'elementor' );
27
32
  const valueLabel = props.valueName || __( 'Value', 'elementor' );
28
33
 
29
- const keyValue = value?.key?.value || '';
30
- const valueValue = value?.value?.value || '';
31
-
32
34
  const [ keyRegex, valueRegex, errMsg ] = useMemo< [ RegExp | undefined, RegExp | undefined, string ] >(
33
35
  () => [
34
36
  props.regexKey ? new RegExp( props.regexKey ) : undefined,
@@ -38,28 +40,44 @@ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {}
38
40
  [ props.regexKey, props.regexValue, props.validationErrorMessage ]
39
41
  );
40
42
 
41
- const validate = ( newValue: string, FieldType: string ): void => {
42
- if ( FieldType === 'key' && keyRegex ) {
43
+ const validate = ( newValue: string, fieldType: string ): boolean => {
44
+ if ( fieldType === 'key' && keyRegex ) {
43
45
  const isValid = keyRegex.test( newValue );
44
46
  setKeyError( isValid ? null : errMsg );
45
- } else if ( FieldType === 'value' && valueRegex ) {
47
+ return isValid;
48
+ } else if ( fieldType === 'value' && valueRegex ) {
46
49
  const isValid = valueRegex.test( newValue );
47
50
  setValueError( isValid ? null : errMsg );
51
+ return isValid;
48
52
  }
53
+ return true;
49
54
  };
50
55
 
51
56
  const handleChange = ( event: ChangeEvent< HTMLInputElement >, fieldType: FieldType ) => {
52
57
  const newValue = event.target.value;
53
58
 
54
- validate( newValue, fieldType );
59
+ setSessionState( ( prev ) => ( {
60
+ ...prev,
61
+ [ fieldType ]: newValue,
62
+ } ) );
55
63
 
56
- setValue( {
57
- ...value,
58
- [ fieldType ]: {
59
- value: newValue,
60
- $$type: 'string',
61
- },
62
- } );
64
+ if ( validate( newValue, fieldType ) ) {
65
+ setValue( {
66
+ ...value,
67
+ [ fieldType ]: {
68
+ value: newValue,
69
+ $$type: 'string',
70
+ },
71
+ } );
72
+ } else {
73
+ setValue( {
74
+ ...value,
75
+ [ fieldType ]: {
76
+ value: '',
77
+ $$type: 'string',
78
+ },
79
+ } );
80
+ }
63
81
  };
64
82
 
65
83
  const isKeyInvalid = keyError !== null;
@@ -67,14 +85,16 @@ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {}
67
85
 
68
86
  return (
69
87
  <ControlActions>
70
- <Grid container gap={ 1.5 } p={ 1.5 } sx={ props.sx }>
88
+ <Grid container gap={ 1.5 }>
71
89
  <Grid item xs={ 12 }>
72
90
  <FormLabel size="tiny">{ keyLabel }</FormLabel>
73
91
  <TextField
92
+ // eslint-disable-next-line jsx-a11y/no-autofocus
93
+ autoFocus
74
94
  sx={ { pt: 1 } }
75
95
  size="tiny"
76
96
  fullWidth
77
- value={ keyValue }
97
+ value={ sessionState.key }
78
98
  onChange={ ( e: ChangeEvent< HTMLInputElement > ) => handleChange( e, 'key' ) }
79
99
  error={ isKeyInvalid }
80
100
  />
@@ -86,7 +106,7 @@ export const KeyValueControl = createControl( ( props: KeyValueControlProps = {}
86
106
  sx={ { pt: 1 } }
87
107
  size="tiny"
88
108
  fullWidth
89
- value={ valueValue }
109
+ value={ sessionState.value }
90
110
  onChange={ ( e: ChangeEvent< HTMLInputElement > ) => handleChange( e, 'value' ) }
91
111
  disabled={ isKeyInvalid }
92
112
  error={ isValueInvalid }
@@ -38,6 +38,7 @@ type Props = ControlProps< {
38
38
  allowCustomValues?: boolean;
39
39
  minInputLength?: number;
40
40
  placeholder?: string;
41
+ label?: string;
41
42
  } >;
42
43
 
43
44
  type LinkSessionValue = {
@@ -66,6 +67,7 @@ export const LinkControl = createControl( ( props: Props ) => {
66
67
  placeholder,
67
68
  minInputLength = 2,
68
69
  context: { elementId },
70
+ label = __( 'Link', 'elementor' ),
69
71
  } = props || {};
70
72
 
71
73
  const [ linkInLinkRestriction, setLinkInLinkRestriction ] = useState( getLinkInLinkRestriction( elementId ) );
@@ -163,7 +165,7 @@ export const LinkControl = createControl( ( props: Props ) => {
163
165
  marginInlineEnd: -0.75,
164
166
  } }
165
167
  >
166
- <ControlFormLabel>{ __( 'Link', 'elementor' ) }</ControlFormLabel>
168
+ <ControlFormLabel>{ label }</ControlFormLabel>
167
169
  <ConditionalInfoTip isVisible={ ! isActive } linkInLinkRestriction={ linkInLinkRestriction }>
168
170
  <ToggleIconControl
169
171
  disabled={ shouldDisableAddingLink }
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, useRef } from 'react';
2
+ import { type RefObject, useRef } from 'react';
3
3
  import { dimensionsPropTypeUtil, type PropKey, sizePropTypeUtil } from '@elementor/editor-props';
4
4
  import { isExperimentActive } from '@elementor/editor-v1-adapters';
5
5
  import { DetachIcon, LinkIcon, SideBottomIcon, SideLeftIcon, SideRightIcon, SideTopIcon } from '@elementor/icons';
@@ -24,7 +24,7 @@ export const LinkedDimensionsControl = createControl(
24
24
  extendedOptions?: ExtendedOption[];
25
25
  } ) => {
26
26
  const { value: sizeValue, setValue: setSizeValue, disabled: sizeDisabled } = useBoundProp( sizePropTypeUtil );
27
- const gridRowRefs: MutableRefObject< HTMLElement | undefined >[] = [ useRef(), useRef() ];
27
+ const gridRowRefs: RefObject< HTMLDivElement >[] = [ useRef( null ), useRef( null ) ];
28
28
 
29
29
  const {
30
30
  value: dimensionsValue,
@@ -127,7 +127,7 @@ const Control = ( {
127
127
  startIcon: React.ReactNode;
128
128
  isLinked: boolean;
129
129
  extendedOptions?: ExtendedOption[];
130
- anchorRef: MutableRefObject< HTMLElement | undefined >;
130
+ anchorRef: RefObject< HTMLDivElement >;
131
131
  } ) => {
132
132
  if ( isLinked ) {
133
133
  return <SizeControl startIcon={ startIcon } extendedOptions={ extendedOptions } anchorRef={ anchorRef } />;
@@ -13,7 +13,7 @@ const RESTRICTED_INPUT_KEYS = [ 'e', 'E', '+', '-' ];
13
13
 
14
14
  export const NumberControl = createControl(
15
15
  ( {
16
- placeholder,
16
+ placeholder: labelPlaceholder,
17
17
  max = Number.MAX_VALUE,
18
18
  min = -Number.MAX_VALUE,
19
19
  step = 1,
@@ -25,7 +25,7 @@ export const NumberControl = createControl(
25
25
  step?: number;
26
26
  shouldForceInt?: boolean;
27
27
  } ) => {
28
- const { value, setValue, disabled } = useBoundProp( numberPropTypeUtil );
28
+ const { value, setValue, placeholder, disabled } = useBoundProp( numberPropTypeUtil );
29
29
 
30
30
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
31
31
  const eventValue: string = event.target.value;
@@ -50,7 +50,7 @@ export const NumberControl = createControl(
50
50
  disabled={ disabled }
51
51
  value={ isEmptyOrNaN( value ) ? '' : value }
52
52
  onChange={ handleChange }
53
- placeholder={ placeholder }
53
+ placeholder={ labelPlaceholder ?? ( placeholder ? String( placeholder ) : '' ) }
54
54
  inputProps={ { step } }
55
55
  onKeyDown={ ( event: KeyboardEvent ) => {
56
56
  if ( RESTRICTED_INPUT_KEYS.includes( event.key ) ) {
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import { useMemo } from 'react';
3
3
  import { createArrayPropUtils, type PropKey } from '@elementor/editor-props';
4
- import { __ } from '@wordpress/i18n';
4
+ import { Box } from '@elementor/ui';
5
5
 
6
+ /* eslint-disable */
6
7
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
7
8
  import { PopoverContent } from '../components/popover-content';
8
9
  import { PopoverGridContainer } from '../components/popover-grid-container';
@@ -16,13 +17,25 @@ import {
16
17
 
17
18
  type RepeatableControlProps = {
18
19
  label: string;
20
+ repeaterLabel: string;
19
21
  childControlConfig: ChildControlConfig;
20
22
  showDuplicate?: boolean;
21
23
  showToggle?: boolean;
24
+ initialValues?: object;
25
+ patternLabel?: string;
26
+ placeholder?: string;
22
27
  };
23
28
 
24
29
  export const RepeatableControl = createControl(
25
- ( { label, childControlConfig, showDuplicate, showToggle }: RepeatableControlProps ) => {
30
+ ( {
31
+ repeaterLabel,
32
+ childControlConfig,
33
+ showDuplicate,
34
+ showToggle,
35
+ initialValues,
36
+ patternLabel,
37
+ placeholder,
38
+ }: RepeatableControlProps ) => {
26
39
  const { propTypeUtil: childPropTypeUtil } = childControlConfig;
27
40
 
28
41
  if ( ! childPropTypeUtil ) {
@@ -35,6 +48,20 @@ export const RepeatableControl = createControl(
35
48
  );
36
49
 
37
50
  const { propType, value, setValue } = useBoundProp( childArrayPropTypeUtil );
51
+ const ItemLabel = ( { value: itemValue }: { value: any } ) => {
52
+ const pattern = patternLabel || '';
53
+ const finalLabel = interpolate( pattern, itemValue.value );
54
+
55
+ if ( finalLabel ) {
56
+ return <span>{ finalLabel }</span>;
57
+ }
58
+
59
+ return (
60
+ <Box component="span" color="text.tertiary">
61
+ { placeholder }
62
+ </Box>
63
+ );
64
+ };
38
65
 
39
66
  return (
40
67
  <PropProvider propType={ propType } value={ value } setValue={ setValue }>
@@ -43,12 +70,13 @@ export const RepeatableControl = createControl(
43
70
  openOnAdd
44
71
  values={ value ?? [] }
45
72
  setValues={ setValue }
46
- label={ label }
73
+ label={ repeaterLabel }
74
+ isSortable={ false }
47
75
  itemSettings={ {
48
76
  Icon: ItemIcon,
49
77
  Label: ItemLabel,
50
78
  Content: ItemContent,
51
- initialValues: childPropTypeUtil.create( null ),
79
+ initialValues: childPropTypeUtil.create( initialValues || null ),
52
80
  } }
53
81
  showDuplicate={ showDuplicate }
54
82
  showToggle={ showToggle }
@@ -82,8 +110,10 @@ const Content = () => {
82
110
  );
83
111
  };
84
112
 
85
- const ItemLabel = () => {
86
- const { label = __( 'Empty', 'elementor' ) } = useRepeatableControlContext();
87
-
88
- return <span>{ label }</span>;
113
+ const interpolate = ( template: string, data: object ) => {
114
+ //remove it to be generic
115
+ if ( Object.values( data ).some( ( value ) => value.value === '' || value === '' ) ) {
116
+ return null;
117
+ }
118
+ return new Function( ...Object.keys( data ), `return \`${ template }\`;` )( ...Object.values( data ) );
89
119
  };
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, useEffect, useState } from 'react';
2
+ import { type RefObject, useEffect, useState } from 'react';
3
3
  import { sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
4
4
  import { useActiveBreakpoint } from '@elementor/editor-responsive';
5
5
  import { usePopupState } from '@elementor/ui';
@@ -23,7 +23,7 @@ type SizeControlProps = {
23
23
  units?: Unit[];
24
24
  extendedOptions?: ExtendedOption[];
25
25
  disableCustom?: boolean;
26
- anchorRef?: MutableRefObject< HTMLElement | undefined >;
26
+ anchorRef?: RefObject< HTMLDivElement | null >;
27
27
  defaultUnit?: Unit;
28
28
  };
29
29
 
@@ -149,7 +149,7 @@ export const SizeControl = createControl( ( props: SizeControlProps ) => {
149
149
  { anchorRef?.current && (
150
150
  <TextFieldPopover
151
151
  popupState={ popupState }
152
- anchorRef={ anchorRef as MutableRefObject< HTMLElement > }
152
+ anchorRef={ anchorRef }
153
153
  restoreValue={ restoreValue }
154
154
  value={ controlSize as string }
155
155
  onChange={ handleSizeChange }
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { forwardRef, type MutableRefObject, useRef } from 'react';
2
+ import { forwardRef, useRef } from 'react';
3
3
  import { strokePropTypeUtil } from '@elementor/editor-props';
4
4
  import { Grid } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
@@ -22,7 +22,7 @@ const units: Unit[] = [ 'px', 'em', 'rem' ];
22
22
 
23
23
  export const StrokeControl = createControl( () => {
24
24
  const propContext = useBoundProp( strokePropTypeUtil );
25
- const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
25
+ const rowRef = useRef< HTMLDivElement >( null );
26
26
 
27
27
  return (
28
28
  <PropProvider { ...propContext }>
@@ -7,7 +7,6 @@ import { type OpenOptions, useWpMediaAttachment, useWpMediaFrame } from '@elemen
7
7
  import { __ } from '@wordpress/i18n';
8
8
 
9
9
  import { useBoundProp } from '../bound-prop-context';
10
- import { ControlFormLabel } from '../components/control-form-label';
11
10
  import { EnableUnfilteredModal } from '../components/enable-unfiltered-modal';
12
11
  import ControlActions from '../control-actions/control-actions';
13
12
  import { createControl } from '../create-control';
@@ -83,7 +82,6 @@ export const SvgMediaControl = createControl( () => {
83
82
  return (
84
83
  <Stack gap={ 1 }>
85
84
  <EnableUnfilteredModal open={ unfilteredModalOpenState } onClose={ onCloseUnfilteredModal } />
86
- <ControlFormLabel> { __( 'SVG', 'elementor' ) } </ControlFormLabel>
87
85
  <ControlActions>
88
86
  <StyledCard variant="outlined">
89
87
  <StyledCardMediaContainer>
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export { TextAreaControl } from './controls/text-area-control';
5
5
  export { SizeControl } from './controls/size-control';
6
6
  export { StrokeControl } from './controls/stroke-control';
7
7
  export { BoxShadowRepeaterControl } from './controls/box-shadow-repeater-control';
8
+ export { FilterRepeaterControl } from './controls/filter-repeater-control';
8
9
  export { SelectControl } from './controls/select-control';
9
10
  export { ColorControl } from './controls/color-control';
10
11
  export { ToggleControl } from './controls/toggle-control';
@@ -22,6 +23,7 @@ export { SwitchControl } from './controls/switch-control';
22
23
  export { RepeatableControl } from './controls/repeatable-control';
23
24
  export { KeyValueControl } from './controls/key-value-control';
24
25
  export { PositionControl } from './controls/position-control';
26
+ export { PopoverContent } from './components/popover-content';
25
27
 
26
28
  // components
27
29
  export { ControlFormLabel } from './components/control-form-label';
@@ -35,7 +37,7 @@ export type { EqualUnequalItems } from './controls/equal-unequal-sizes-control';
35
37
  export type { ControlActionsItems } from './control-actions/control-actions-context';
36
38
  export type { PropProviderProps } from './bound-prop-context';
37
39
  export type { SetValue } from './bound-prop-context/prop-context';
38
- export type { ExtendedOption } from './utils/size-control';
40
+ export type { ExtendedOption, Unit } from './utils/size-control';
39
41
  export type { ToggleControlProps } from './controls/toggle-control';
40
42
  export type { FontCategory } from './controls/font-family-control/font-family-control';
41
43