@elementor/editor-controls 4.1.0-744 → 4.1.0-746

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-controls",
3
3
  "description": "This package contains the controls model and utils for the Elementor editor",
4
- "version": "4.1.0-744",
4
+ "version": "4.1.0-746",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,22 +40,22 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-current-user": "4.1.0-744",
44
- "@elementor/editor-elements": "4.1.0-744",
45
- "@elementor/editor-props": "4.1.0-744",
46
- "@elementor/editor-responsive": "4.1.0-744",
47
- "@elementor/editor-ui": "4.1.0-744",
48
- "@elementor/editor-v1-adapters": "4.1.0-744",
49
- "@elementor/env": "4.1.0-744",
50
- "@elementor/http-client": "4.1.0-744",
43
+ "@elementor/editor-current-user": "4.1.0-746",
44
+ "@elementor/editor-elements": "4.1.0-746",
45
+ "@elementor/editor-props": "4.1.0-746",
46
+ "@elementor/editor-responsive": "4.1.0-746",
47
+ "@elementor/editor-ui": "4.1.0-746",
48
+ "@elementor/editor-v1-adapters": "4.1.0-746",
49
+ "@elementor/env": "4.1.0-746",
50
+ "@elementor/http-client": "4.1.0-746",
51
51
  "@elementor/icons": "^1.68.0",
52
- "@elementor/locations": "4.1.0-744",
53
- "@elementor/events": "4.1.0-744",
54
- "@elementor/query": "4.1.0-744",
55
- "@elementor/session": "4.1.0-744",
52
+ "@elementor/locations": "4.1.0-746",
53
+ "@elementor/events": "4.1.0-746",
54
+ "@elementor/query": "4.1.0-746",
55
+ "@elementor/session": "4.1.0-746",
56
56
  "@elementor/ui": "1.36.17",
57
- "@elementor/utils": "4.1.0-744",
58
- "@elementor/wp-media": "4.1.0-744",
57
+ "@elementor/utils": "4.1.0-746",
58
+ "@elementor/wp-media": "4.1.0-746",
59
59
  "@wordpress/i18n": "^5.13.0",
60
60
  "@monaco-editor/react": "^4.7.0",
61
61
  "dayjs": "^1.11.18",
@@ -89,12 +89,21 @@ const ArrayPropKeyProvider = ( { children, bind }: PropKeyProviderProps ) => {
89
89
  };
90
90
 
91
91
  const value = context.value?.[ Number( bind ) ];
92
+ const placeholder = context.placeholder?.[ Number( bind ) ];
92
93
 
93
94
  const propType = context.propType.item_prop_type;
94
95
 
95
96
  return (
96
97
  <PropKeyContext.Provider
97
- value={ { ...context, value, setValue, bind, propType, path: [ ...( path ?? [] ), bind ] } }
98
+ value={ {
99
+ ...context,
100
+ value,
101
+ setValue,
102
+ bind,
103
+ propType,
104
+ path: [ ...( path ?? [] ), bind ],
105
+ placeholder: placeholder ?? undefined,
106
+ } }
98
107
  >
99
108
  { children }
100
109
  </PropKeyContext.Provider>
@@ -73,7 +73,10 @@ export function useBoundProp< TKey extends string, TValue extends PropValue >(
73
73
 
74
74
  const propType = resolveUnionPropType( propKeyContext.propType, propTypeUtil.key );
75
75
 
76
- const value = propTypeUtil.extract( propKeyContext.value ?? propType.default ?? null );
76
+ const hasPlaceholder = propKeyContext.placeholder !== undefined && propKeyContext.placeholder !== null;
77
+ const fallbackValue = hasPlaceholder ? null : propType.default;
78
+
79
+ const value = propTypeUtil.extract( propKeyContext.value ?? fallbackValue ?? null );
77
80
  const placeholder = propTypeUtil.extract( propKeyContext.placeholder ?? null );
78
81
 
79
82
  return {
@@ -25,6 +25,7 @@ const UNDERLINE_KEYBOARD_SHORTCUT = 'u';
25
25
  type InlineEditorProps = {
26
26
  value: string | null;
27
27
  setValue: ( value: string | null ) => void;
28
+ placeholder?: string | null;
28
29
  editorProps?: EditorProps;
29
30
  elementClasses?: string;
30
31
  sx?: SxProps< Theme >;
@@ -41,6 +42,7 @@ export const InlineEditor = React.forwardRef( ( props: InlineEditorProps, ref )
41
42
  const {
42
43
  value,
43
44
  setValue,
45
+ placeholder = null,
44
46
  editorProps = {},
45
47
  elementClasses = '',
46
48
  autofocus = false,
@@ -139,6 +141,8 @@ export const InlineEditor = React.forwardRef( ( props: InlineEditorProps, ref )
139
141
  attributes: {
140
142
  ...( editorProps.attributes ?? {} ),
141
143
  role: 'textbox',
144
+ ...( placeholder ? { 'data-placeholder': placeholder } : {} ),
145
+ ...( value === null || value === '' ? { class: 'is-empty' } : {} ),
142
146
  },
143
147
  },
144
148
  onCreate: onEditorCreate ? ( { editor: mountedEditor } ) => onEditorCreate( mountedEditor ) : undefined,
@@ -14,11 +14,12 @@ type ImageMediaControlProps = {
14
14
  };
15
15
 
16
16
  export const ImageMediaControl = createControl( ( { mediaTypes = [ 'image' ] }: ImageMediaControlProps ) => {
17
- const { value, setValue, propType } = useBoundProp( imageSrcPropTypeUtil );
17
+ const { value, setValue, propType, placeholder } = useBoundProp( imageSrcPropTypeUtil );
18
18
  const { id, url } = value ?? {};
19
19
 
20
20
  const { data: attachment, isFetching } = useWpMediaAttachment( id?.value || null );
21
- const src = attachment?.url ?? url?.value ?? null;
21
+ const { data: placeholderAttachment } = useWpMediaAttachment( placeholder?.id?.value || null );
22
+ const src = attachment?.url ?? url?.value ?? placeholderAttachment?.url ?? null;
22
23
 
23
24
  const { open } = useWpMediaFrame( {
24
25
  mediaTypes,
@@ -21,7 +21,7 @@ export const InlineEditingControl = createControl(
21
21
  attributes?: Record< string, string >;
22
22
  props?: ComponentProps< 'div' >;
23
23
  } ) => {
24
- const { value, setValue } = useBoundProp( htmlV3PropTypeUtil );
24
+ const { value, setValue, placeholder } = useBoundProp( htmlV3PropTypeUtil );
25
25
  const content = stringPropTypeUtil.extract( value?.content ?? null ) ?? '';
26
26
 
27
27
  const debouncedParse = useMemo(
@@ -82,6 +82,13 @@ export const InlineEditingControl = createControl(
82
82
  margin: 0,
83
83
  padding: 0,
84
84
  },
85
+ '&.is-empty::before': {
86
+ content: 'attr(data-placeholder)',
87
+ color: 'text.tertiary',
88
+ pointerEvents: 'none',
89
+ position: 'absolute',
90
+ opacity: 0.6,
91
+ },
85
92
  },
86
93
  '.strip-styles *': {
87
94
  all: 'unset',
@@ -91,7 +98,11 @@ export const InlineEditingControl = createControl(
91
98
  { ...attributes }
92
99
  { ...props }
93
100
  >
94
- <InlineEditor value={ content } setValue={ handleChange } />
101
+ <InlineEditor
102
+ value={ content }
103
+ setValue={ handleChange }
104
+ placeholder={ placeholder?.content?.value ?? null }
105
+ />
95
106
  </Box>
96
107
  </ControlActions>
97
108
  );
@@ -42,8 +42,9 @@ const SIZE = 'tiny';
42
42
 
43
43
  export const LinkControl = createControl( ( props: Props ) => {
44
44
  const { value, path, setValue, ...propContext } = useBoundProp( linkPropTypeUtil );
45
+ const linkPlaceholder = propContext.placeholder;
45
46
  const [ linkSessionValue, setLinkSessionValue ] = useSessionStorage< LinkSessionValue >( path.join( '/' ) );
46
- const [ isActive, setIsActive ] = useState( !! value );
47
+ const [ isActive, setIsActive ] = useState( !! value || !! linkPlaceholder );
47
48
 
48
49
  const {
49
50
  allowCustomValues = true,
@@ -56,22 +57,23 @@ export const LinkControl = createControl( ( props: Props ) => {
56
57
  } = props || {};
57
58
 
58
59
  const [ linkInLinkRestriction, setLinkInLinkRestriction ] = useState(
59
- getLinkInLinkRestriction( elementId, value )
60
+ getLinkInLinkRestriction( elementId, value ?? linkPlaceholder )
60
61
  );
62
+
61
63
  const shouldDisableAddingLink = ! isActive && linkInLinkRestriction.shouldRestrict;
62
64
 
63
65
  const debouncedCheckRestriction = useMemo(
64
66
  () =>
65
67
  debounce( () => {
66
- const newRestriction = getLinkInLinkRestriction( elementId, value );
68
+ const newRestriction = getLinkInLinkRestriction( elementId, value ?? linkPlaceholder );
67
69
 
68
- if ( newRestriction.shouldRestrict && isActive ) {
70
+ if ( newRestriction.shouldRestrict && isActive && ! linkPlaceholder ) {
69
71
  setIsActive( false );
70
72
  }
71
73
 
72
74
  setLinkInLinkRestriction( newRestriction );
73
75
  }, 300 ),
74
- [ elementId, isActive, value ]
76
+ [ elementId, isActive, value, linkPlaceholder ]
75
77
  );
76
78
 
77
79
  useEffect( () => {
@@ -94,7 +96,7 @@ export const LinkControl = createControl( ( props: Props ) => {
94
96
  }, [ elementId, debouncedCheckRestriction ] );
95
97
 
96
98
  const onEnabledChange = () => {
97
- setLinkInLinkRestriction( getLinkInLinkRestriction( elementId, value ) );
99
+ setLinkInLinkRestriction( getLinkInLinkRestriction( elementId, value ?? linkPlaceholder ) );
98
100
 
99
101
  if ( linkInLinkRestriction.shouldRestrict && ! isActive ) {
100
102
  return;
@@ -141,12 +143,14 @@ export const LinkControl = createControl( ( props: Props ) => {
141
143
  >
142
144
  <ControlLabel>{ label }</ControlLabel>
143
145
  <RestrictedLinkInfotip isVisible={ ! isActive } linkInLinkRestriction={ linkInLinkRestriction }>
144
- <ToggleIconControl
146
+ <IconButton
147
+ size={ SIZE }
148
+ onClick={ onEnabledChange }
149
+ aria-label={ __( 'Toggle link', 'elementor' ) }
145
150
  disabled={ shouldDisableAddingLink }
146
- active={ isActive }
147
- onIconClick={ onEnabledChange }
148
- label={ __( 'Toggle link', 'elementor' ) }
149
- />
151
+ >
152
+ { isActive ? <MinusIcon fontSize={ SIZE } /> : <PlusIcon fontSize={ SIZE } /> }
153
+ </IconButton>
150
154
  </RestrictedLinkInfotip>
151
155
  </Stack>
152
156
  <Collapse in={ isActive } timeout="auto" unmountOnExit>
@@ -177,18 +181,3 @@ export const LinkControl = createControl( ( props: Props ) => {
177
181
  </PropProvider>
178
182
  );
179
183
  } );
180
-
181
- type ToggleIconControlProps = {
182
- disabled: boolean;
183
- active: boolean;
184
- onIconClick: () => void;
185
- label?: string;
186
- };
187
-
188
- const ToggleIconControl = ( { disabled, active, onIconClick, label }: ToggleIconControlProps ) => {
189
- return (
190
- <IconButton size={ SIZE } onClick={ onIconClick } aria-label={ label } disabled={ disabled }>
191
- { active ? <MinusIcon fontSize={ SIZE } /> : <PlusIcon fontSize={ SIZE } /> }
192
- </IconButton>
193
- );
194
- };
@@ -1,6 +1,12 @@
1
1
  import * as React from 'react';
2
2
  import { useMemo, useState } from 'react';
3
- import { numberPropTypeUtil, stringPropTypeUtil, urlPropTypeUtil } from '@elementor/editor-props';
3
+ import {
4
+ numberPropTypeUtil,
5
+ queryPropTypeUtil,
6
+ type QueryPropValue,
7
+ stringPropTypeUtil,
8
+ urlPropTypeUtil,
9
+ } from '@elementor/editor-props';
4
10
  import { type HttpResponse, httpService } from '@elementor/http-client';
5
11
  import { SearchIcon } from '@elementor/icons';
6
12
  import { debounce } from '@elementor/utils';
@@ -35,56 +41,51 @@ type Response = HttpResponse< { value: FlatOption[] | CategorizedOption[] } >;
35
41
  type FetchOptionsParams = Record< string, unknown > & { term: string };
36
42
 
37
43
  export const QueryControl = createControl( ( props: Props ) => {
38
- const { value, setValue } = useBoundProp< DestinationProp >();
44
+ const { value: queryValue, setValue: setQueryValue } = useBoundProp( queryPropTypeUtil );
45
+ const { value: urlValue, setValue: setUrlValue, placeholder: urlPlaceholder } = useBoundProp( urlPropTypeUtil );
39
46
 
40
47
  const {
41
48
  allowCustomValues = true,
42
49
  queryOptions: { url, params = {} },
43
- placeholder,
50
+ placeholder = __( 'Search', 'elementor' ),
44
51
  minInputLength = 2,
45
52
  onSetValue,
46
53
  ariaLabel,
47
54
  } = props || {};
48
55
 
49
- const normalizedPlaceholder = placeholder || __( 'Search', 'elementor' );
50
-
51
56
  const [ options, setOptions ] = useState< FlatOption[] | CategorizedOption[] >(
52
- generateFirstLoadedOption( value?.value )
57
+ generateFirstLoadedOption( queryValue )
53
58
  );
54
59
 
55
60
  const onOptionChange = ( newValue: number | null ) => {
56
61
  if ( newValue === null ) {
57
- setValue( null );
62
+ setQueryValue( null );
58
63
  onSetValue?.( null );
59
64
 
60
65
  return;
61
66
  }
62
67
 
63
- const valueToSave = {
64
- $$type: 'query',
65
- value: {
66
- id: numberPropTypeUtil.create( newValue ),
67
- label: stringPropTypeUtil.create( findMatchingOption( options, newValue )?.label || null ),
68
- },
68
+ const newQueryValue = {
69
+ id: numberPropTypeUtil.create( newValue ),
70
+ label: stringPropTypeUtil.create( findMatchingOption( options, newValue )?.label || null ),
69
71
  };
70
72
 
71
- setValue( valueToSave );
72
- onSetValue?.( valueToSave );
73
+ setQueryValue( newQueryValue );
74
+ onSetValue?.( queryPropTypeUtil.create( newQueryValue ) );
73
75
  };
74
76
 
75
77
  const onTextChange = ( newValue: string | null ) => {
76
- if ( ! newValue ) {
77
- setValue( null );
78
+ const trimmedValue = newValue?.trim() || '';
79
+
80
+ if ( ! trimmedValue ) {
81
+ setUrlValue( null );
78
82
  onSetValue?.( null );
79
83
 
80
84
  return;
81
85
  }
82
86
 
83
- const newLinkValue = newValue?.trim() || '';
84
- const valueToSave = newLinkValue ? urlPropTypeUtil.create( newLinkValue ) : null;
85
-
86
- setValue( valueToSave );
87
- onSetValue?.( valueToSave );
87
+ setUrlValue( trimmedValue );
88
+ onSetValue?.( urlPropTypeUtil.create( trimmedValue ) );
88
89
  updateOptions( newValue );
89
90
  };
90
91
 
@@ -110,14 +111,16 @@ export const QueryControl = createControl( ( props: Props ) => {
110
111
  [ url ]
111
112
  );
112
113
 
114
+ const displayValue = queryValue?.id?.value ?? urlValue;
115
+
113
116
  return (
114
117
  <ControlActions>
115
118
  <Autocomplete
116
119
  options={ options }
117
120
  allowCustomValues={ allowCustomValues }
118
- placeholder={ normalizedPlaceholder }
121
+ placeholder={ urlPlaceholder ?? placeholder }
119
122
  startAdornment={ <SearchIcon fontSize="tiny" /> }
120
- value={ value?.value?.id?.value || value?.value }
123
+ value={ displayValue }
121
124
  onOptionChange={ onOptionChange }
122
125
  onTextChange={ onTextChange }
123
126
  minInputLength={ minInputLength }
@@ -152,17 +155,15 @@ function formatOptions( options: FlatOption[] | CategorizedOption[] ): FlatOptio
152
155
  );
153
156
  }
154
157
 
155
- function generateFirstLoadedOption( unionValue: DestinationProp | null ): FlatOption[] {
156
- const value = unionValue?.id?.value;
157
- const label = unionValue?.label?.value;
158
- const type = unionValue?.id?.$$type || 'url';
159
-
160
- return value && label && type === 'number'
161
- ? [
162
- {
163
- id: value.toString(),
164
- label,
165
- },
166
- ]
167
- : [];
158
+ function generateFirstLoadedOption( queryValue: QueryPropValue[ 'value' ] | null ): FlatOption[] {
159
+ const id = queryValue?.id?.value;
160
+ const label = queryValue?.label?.value;
161
+
162
+ const option = [];
163
+
164
+ if ( id && label ) {
165
+ option.push( { id: id.toString(), label } );
166
+ }
167
+
168
+ return option;
168
169
  }
@@ -6,7 +6,7 @@ import { useBoundProp } from '../bound-prop-context/use-bound-prop';
6
6
  import { createControl } from '../create-control';
7
7
 
8
8
  export const SwitchControl = createControl( () => {
9
- const { value, setValue, disabled } = useBoundProp( booleanPropTypeUtil );
9
+ const { value, setValue, disabled, placeholder } = useBoundProp( booleanPropTypeUtil );
10
10
 
11
11
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
12
12
  setValue( event.target.checked );
@@ -15,7 +15,7 @@ export const SwitchControl = createControl( () => {
15
15
  return (
16
16
  <Box sx={ { display: 'flex', justifyContent: 'flex-end' } }>
17
17
  <Switch
18
- checked={ !! value }
18
+ checked={ !! ( value || placeholder ) }
19
19
  onChange={ handleChange }
20
20
  size="small"
21
21
  disabled={ disabled }
@@ -11,13 +11,15 @@ type Props = {
11
11
  ariaLabel?: string;
12
12
  };
13
13
 
14
- export const TextAreaControl = createControl( ( { placeholder, ariaLabel }: Props ) => {
15
- const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
14
+ export const TextAreaControl = createControl( ( { placeholder: propPlaceholder, ariaLabel }: Props ) => {
15
+ const { value, setValue, disabled, placeholder: boundPlaceholder } = useBoundProp( stringPropTypeUtil );
16
16
 
17
17
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
18
18
  setValue( event.target.value );
19
19
  };
20
20
 
21
+ const placeholder = propPlaceholder ?? boundPlaceholder ?? undefined;
22
+
21
23
  return (
22
24
  <ControlActions>
23
25
  <TextField
@@ -8,7 +8,7 @@ import { createControl } from '../create-control';
8
8
 
9
9
  export const TextControl = createControl(
10
10
  ( {
11
- placeholder,
11
+ placeholder: propPlaceholder,
12
12
  error,
13
13
  inputValue,
14
14
  inputDisabled,
@@ -24,9 +24,11 @@ export const TextControl = createControl(
24
24
  sx?: SxProps;
25
25
  ariaLabel?: string;
26
26
  } ) => {
27
- const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
27
+ const { value, setValue, disabled, placeholder: boundPlaceholder } = useBoundProp( stringPropTypeUtil );
28
28
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
29
29
 
30
+ const placeholder = propPlaceholder ?? boundPlaceholder ?? undefined;
31
+
30
32
  return (
31
33
  <ControlActions>
32
34
  <TextField
@@ -7,10 +7,12 @@ import ControlActions from '../control-actions/control-actions';
7
7
  import { createControl } from '../create-control';
8
8
 
9
9
  export const UrlControl = createControl(
10
- ( { placeholder, ariaLabel }: { placeholder?: string; ariaLabel?: string } ) => {
11
- const { value, setValue, disabled } = useBoundProp( urlPropTypeUtil );
10
+ ( { placeholder: propPlaceholder, ariaLabel }: { placeholder?: string; ariaLabel?: string } ) => {
11
+ const { value, setValue, disabled, placeholder: boundPlaceholder } = useBoundProp( urlPropTypeUtil );
12
12
  const handleChange = ( event: React.ChangeEvent< HTMLInputElement > ) => setValue( event.target.value );
13
13
 
14
+ const placeholder = propPlaceholder ?? boundPlaceholder ?? undefined;
15
+
14
16
  return (
15
17
  <ControlActions>
16
18
  <TextField