@elementor/editor-controls 4.1.0-769 → 4.1.0-771

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-769",
4
+ "version": "4.1.0-771",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -40,40 +40,41 @@
40
40
  "dev": "tsup --config=../../tsup.dev.ts"
41
41
  },
42
42
  "dependencies": {
43
- "@elementor/editor-current-user": "4.1.0-769",
44
- "@elementor/editor-elements": "4.1.0-769",
45
- "@elementor/editor-props": "4.1.0-769",
46
- "@elementor/editor-responsive": "4.1.0-769",
47
- "@elementor/editor-ui": "4.1.0-769",
48
- "@elementor/editor-v1-adapters": "4.1.0-769",
49
- "@elementor/env": "4.1.0-769",
50
- "@elementor/http-client": "4.1.0-769",
43
+ "@elementor/editor-current-user": "4.1.0-771",
44
+ "@elementor/editor-elements": "4.1.0-771",
45
+ "@elementor/editor-props": "4.1.0-771",
46
+ "@elementor/editor-responsive": "4.1.0-771",
47
+ "@elementor/editor-ui": "4.1.0-771",
48
+ "@elementor/editor-v1-adapters": "4.1.0-771",
49
+ "@elementor/env": "4.1.0-771",
50
+ "@elementor/events": "4.1.0-771",
51
+ "@elementor/http-client": "4.1.0-771",
51
52
  "@elementor/icons": "^1.68.0",
52
- "@elementor/locations": "4.1.0-769",
53
- "@elementor/events": "4.1.0-769",
54
- "@elementor/query": "4.1.0-769",
55
- "@elementor/session": "4.1.0-769",
53
+ "@elementor/locations": "4.1.0-771",
54
+ "@elementor/query": "4.1.0-771",
55
+ "@elementor/session": "4.1.0-771",
56
56
  "@elementor/ui": "1.37.5",
57
- "@elementor/utils": "4.1.0-769",
58
- "@elementor/wp-media": "4.1.0-769",
59
- "@wordpress/i18n": "^5.13.0",
57
+ "@elementor/utils": "4.1.0-771",
58
+ "@elementor/wp-media": "4.1.0-771",
60
59
  "@monaco-editor/react": "^4.7.0",
61
- "dayjs": "^1.11.18",
62
60
  "@tiptap/extension-bold": "^3.11.1",
63
61
  "@tiptap/extension-document": "^3.11.1",
64
62
  "@tiptap/extension-hard-break": "^3.11.1",
65
63
  "@tiptap/extension-heading": "^3.10.4",
66
64
  "@tiptap/extension-italic": "^3.11.1",
65
+ "@tiptap/extension-link": "^3.11.1",
67
66
  "@tiptap/extension-paragraph": "^3.10.4",
68
67
  "@tiptap/extension-strike": "^3.11.1",
69
68
  "@tiptap/extension-subscript": "^3.11.1",
70
69
  "@tiptap/extension-superscript": "^3.11.1",
71
70
  "@tiptap/extension-text": "^3.11.1",
72
71
  "@tiptap/extension-underline": "^3.11.1",
73
- "@tiptap/extension-link": "^3.11.1",
74
72
  "@tiptap/pm": "^3.11.1",
75
73
  "@tiptap/react": "^3.11.1",
76
- "@tiptap/starter-kit": "^3.11.1"
74
+ "@tiptap/starter-kit": "^3.11.1",
75
+ "@wordpress/i18n": "^5.13.0",
76
+ "dayjs": "^1.11.18",
77
+ "primereact": "^10.9.7"
77
78
  },
78
79
  "devDependencies": {
79
80
  "monaco-types": "^0.1.0",
@@ -1,16 +1,18 @@
1
1
  import * as React from 'react';
2
2
  import { emailPropTypeUtil } from '@elementor/editor-props';
3
- import { CollapsibleContent } from '@elementor/editor-ui';
3
+ import { CollapsibleContent, InfoAlert } from '@elementor/editor-ui';
4
4
  import { Box, Divider, Grid, Stack } from '@elementor/ui';
5
+ import { hasProInstalled, isVersionGreaterOrEqual } from '@elementor/utils';
5
6
  import { __ } from '@wordpress/i18n';
6
7
 
7
8
  import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
8
9
  import { ControlFormLabel } from '../components/control-form-label';
9
10
  import { ControlLabel } from '../components/control-label';
10
11
  import { createControl } from '../create-control';
12
+ import { useFormFieldSuggestions } from '../hooks/use-form-field-suggestions';
11
13
  import { ChipsControl } from './chips-control';
14
+ import { MentionTextAreaControl } from './mention-text-area-control';
12
15
  import { SelectControl } from './select-control';
13
- import { TextAreaControl } from './text-area-control';
14
16
  import { TextControl } from './text-control';
15
17
 
16
18
  const EmailField = ( { bind, label, placeholder }: { bind: string; label: string; placeholder?: string } ) => (
@@ -38,23 +40,48 @@ const SubjectField = () => (
38
40
  />
39
41
  );
40
42
 
41
- const MessageField = () => (
42
- <PropKeyProvider bind="message">
43
- <Grid container direction="column" gap={ 0.5 }>
44
- <Grid item>
45
- <ControlFormLabel>{ __( 'Message', 'elementor' ) }</ControlFormLabel>
46
- </Grid>
47
- <Grid item>
48
- <TextAreaControl
49
- placeholder={ __(
50
- 'By default, all form fields are sent via [all-fields] shortcode.',
51
- 'elementor'
52
- ) }
53
- />
43
+ const MIN_PRO_VERSION_FOR_MENTIONS = '4.1.0';
44
+
45
+ const shouldShowMentionsInfo = (): boolean => {
46
+ if ( ! hasProInstalled() ) {
47
+ return true;
48
+ }
49
+
50
+ const proVersion = window.elementorPro?.config?.version;
51
+
52
+ if ( ! proVersion ) {
53
+ return false;
54
+ }
55
+
56
+ return isVersionGreaterOrEqual( proVersion, MIN_PRO_VERSION_FOR_MENTIONS );
57
+ };
58
+
59
+ const MessageField = () => {
60
+ const suggestions = useFormFieldSuggestions();
61
+
62
+ return (
63
+ <PropKeyProvider bind="message">
64
+ <Grid container direction="column" gap={ 0.5 }>
65
+ <Grid item>
66
+ <ControlFormLabel>{ __( 'Message', 'elementor' ) }</ControlFormLabel>
67
+ </Grid>
68
+ <Grid item>
69
+ <MentionTextAreaControl suggestions={ suggestions } />
70
+ </Grid>
71
+ <Grid item>
72
+ <InfoAlert>
73
+ { shouldShowMentionsInfo()
74
+ ? __(
75
+ '[all-fields] shortcode sends all fields. Type @ to insert specific fields and customize your message.',
76
+ 'elementor'
77
+ )
78
+ : __( '[all-fields] shortcode sends all fields.', 'elementor' ) }
79
+ </InfoAlert>
80
+ </Grid>
54
81
  </Grid>
55
- </Grid>
56
- </PropKeyProvider>
57
- );
82
+ </PropKeyProvider>
83
+ );
84
+ };
58
85
 
59
86
  const FromEmailField = () => (
60
87
  <EmailField
@@ -0,0 +1,151 @@
1
+ import * as React from 'react';
2
+ import { useCallback, useState } from 'react';
3
+ import { Mention } from 'primereact/mention';
4
+ import { stringPropTypeUtil } from '@elementor/editor-props';
5
+ import { styled } from '@elementor/ui';
6
+
7
+ import { useBoundProp } from '../bound-prop-context';
8
+ import ControlActions from '../control-actions/control-actions';
9
+ import { createControl } from '../create-control';
10
+ import { type Suggestion } from '../hooks/use-form-field-suggestions';
11
+
12
+ const MentionWrapper = styled( 'div' )( ( { theme } ) => ( {
13
+ position: 'relative',
14
+ '& .p-mention': {
15
+ width: '100%',
16
+ position: 'relative',
17
+ },
18
+ '& textarea': {
19
+ width: '100%',
20
+ boxSizing: 'border-box',
21
+ fontFamily: 'inherit',
22
+ fontSize: theme.typography.pxToRem( 12 ),
23
+ lineHeight: 1.4375,
24
+ padding: '4px 8px',
25
+ borderRadius: theme.shape.borderRadius,
26
+ border: `1px solid ${ theme.palette.divider }`,
27
+ backgroundColor: 'transparent',
28
+ color: 'inherit',
29
+ resize: 'vertical',
30
+ outline: 'none',
31
+ transition: 'border-color 150ms ease-in-out',
32
+ '&:hover': {
33
+ borderColor: theme.palette.action.active,
34
+ },
35
+ '&:focus': {
36
+ borderColor: theme.palette.primary.main,
37
+ borderWidth: 2,
38
+ padding: '3px 7px',
39
+ },
40
+ '&:disabled': {
41
+ opacity: 0.38,
42
+ cursor: 'default',
43
+ },
44
+ '&::placeholder': {
45
+ color: 'inherit',
46
+ opacity: 0.5,
47
+ },
48
+ },
49
+ '& .p-mention-panel': {
50
+ fontFamily: 'inherit',
51
+ fontSize: theme.typography.pxToRem( 12 ),
52
+ backgroundColor: theme.palette.background.paper,
53
+ border: `1px solid ${ theme.palette.divider }`,
54
+ borderRadius: theme.shape.borderRadius,
55
+ boxShadow: theme.shadows[ 4 ],
56
+ maxHeight: '200px',
57
+ overflow: 'auto',
58
+ zIndex: theme.zIndex.modal,
59
+ maxWidth: '100%',
60
+ right: 0,
61
+ left: 'auto !important',
62
+ },
63
+ '& .p-mention-items': {
64
+ listStyle: 'none',
65
+ margin: 0,
66
+ padding: '4px 0',
67
+ },
68
+ '& .p-mention-item': {
69
+ padding: '6px 12px',
70
+ cursor: 'pointer',
71
+ color: theme.palette.text.primary,
72
+ '&:hover': {
73
+ backgroundColor: theme.palette.action.hover,
74
+ },
75
+ '&.p-highlight': {
76
+ backgroundColor: theme.palette.action.selected,
77
+ },
78
+ },
79
+ } ) );
80
+
81
+ type Props = {
82
+ placeholder?: string;
83
+ ariaLabel?: string;
84
+ suggestions: Suggestion[];
85
+ };
86
+
87
+ export const MentionTextAreaControl = createControl(
88
+ ( { placeholder, ariaLabel, suggestions: allSuggestions }: Props ) => {
89
+ const { value, setValue, disabled } = useBoundProp( stringPropTypeUtil );
90
+ const [ filteredSuggestions, setFilteredSuggestions ] = useState< Suggestion[] >( [] );
91
+
92
+ const transformMentionsToShortcodes = useCallback(
93
+ ( text: string ): string => {
94
+ let result = text;
95
+
96
+ for ( const suggestion of allSuggestions ) {
97
+ const mentionPattern = new RegExp(
98
+ `@${ suggestion.value.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ) }(?=\\s|$|[^a-zA-Z0-9_-])`,
99
+ 'g'
100
+ );
101
+ result = result.replace( mentionPattern, `[${ suggestion.value }]` );
102
+ }
103
+
104
+ return result;
105
+ },
106
+ [ allSuggestions ]
107
+ );
108
+
109
+ const handleChange = useCallback(
110
+ ( e: React.FormEvent< HTMLInputElement > ) => {
111
+ const rawValue = ( e.target as HTMLTextAreaElement ).value;
112
+ const transformed = transformMentionsToShortcodes( rawValue );
113
+ setValue( transformed );
114
+ },
115
+ [ setValue, transformMentionsToShortcodes ]
116
+ );
117
+
118
+ const handleSearch = useCallback(
119
+ ( event: { query: string } ) => {
120
+ const query = event.query.toLowerCase();
121
+ const filtered = allSuggestions.filter(
122
+ ( item ) => item.label.toLowerCase().includes( query ) || item.value.toLowerCase().includes( query )
123
+ );
124
+ setFilteredSuggestions( filtered );
125
+ },
126
+ [ allSuggestions ]
127
+ );
128
+
129
+ return (
130
+ <ControlActions>
131
+ <MentionWrapper>
132
+ <Mention
133
+ value={ value ?? '' }
134
+ onChange={ handleChange }
135
+ suggestions={ filteredSuggestions }
136
+ onSearch={ handleSearch }
137
+ field="value"
138
+ trigger="@"
139
+ rows={ 5 }
140
+ disabled={ disabled }
141
+ placeholder={ placeholder }
142
+ itemTemplate={ SuggestionItem }
143
+ { ...( ariaLabel ? { 'aria-label': ariaLabel } : {} ) }
144
+ />
145
+ </MentionWrapper>
146
+ </ControlActions>
147
+ );
148
+ }
149
+ );
150
+
151
+ const SuggestionItem = ( suggestion: Suggestion ) => <span>{ suggestion.label }</span>;
@@ -26,7 +26,7 @@ export const StrokeControl = createControl( () => {
26
26
 
27
27
  return (
28
28
  <PropProvider { ...propContext }>
29
- <SectionContent>
29
+ <SectionContent gap={ 2 }>
30
30
  <Control bind="width" label={ __( 'Stroke width', 'elementor' ) } ref={ rowRef }>
31
31
  <SizeControl units={ units } anchorRef={ rowRef } />
32
32
  </Control>
@@ -0,0 +1,55 @@
1
+ import { getContainer, getSelectedElements } from '@elementor/editor-elements';
2
+ import { isTransformable } from '@elementor/editor-props';
3
+ import { __privateUseListenTo as useListenTo, commandEndEvent, v1ReadyEvent } from '@elementor/editor-v1-adapters';
4
+
5
+ export type Suggestion = {
6
+ label: string;
7
+ value: string;
8
+ };
9
+
10
+ const FORM_FIELD_WIDGET_TYPES = [ 'e-form-input', 'e-form-textarea', 'e-form-checkbox' ];
11
+
12
+ export function useFormFieldSuggestions(): Suggestion[] {
13
+ return useListenTo(
14
+ [
15
+ v1ReadyEvent(),
16
+ commandEndEvent( 'document/elements/create' ),
17
+ commandEndEvent( 'document/elements/delete' ),
18
+ commandEndEvent( 'document/elements/set-settings' ),
19
+ ],
20
+ () => {
21
+ const selectedElements = getSelectedElements();
22
+ const formElement = selectedElements[ 0 ];
23
+
24
+ if ( ! formElement ) {
25
+ return [];
26
+ }
27
+
28
+ const container = getContainer( formElement.id );
29
+
30
+ if ( ! container?.children ) {
31
+ return [];
32
+ }
33
+
34
+ const suggestions: Suggestion[] = [];
35
+
36
+ container.children.forEachRecursive?.( ( child ) => {
37
+ const widgetType = child.model.get( 'widgetType' ) as string | undefined;
38
+
39
+ if ( ! widgetType || ! FORM_FIELD_WIDGET_TYPES.includes( widgetType ) ) {
40
+ return;
41
+ }
42
+
43
+ const cssIdProp = child.settings.get( '_cssid' );
44
+ const fieldId = isTransformable( cssIdProp ) ? cssIdProp.value : cssIdProp;
45
+
46
+ if ( fieldId && typeof fieldId === 'string' ) {
47
+ suggestions.push( { label: fieldId, value: fieldId } );
48
+ }
49
+ } );
50
+
51
+ return suggestions;
52
+ },
53
+ []
54
+ );
55
+ }
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  export { ImageControl } from './controls/image-control';
3
3
  export { TextControl } from './controls/text-control';
4
4
  export { TextAreaControl } from './controls/text-area-control';
5
+ export { MentionTextAreaControl } from './controls/mention-text-area-control';
5
6
  export { SizeControl } from './controls/size-control';
6
7
  export { StrokeControl } from './controls/stroke-control';
7
8
  export { BoxShadowRepeaterControl } from './controls/box-shadow-repeater-control';