@elementor/editor-controls 3.35.0-326 → 3.35.0-328

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": "3.35.0-326",
4
+ "version": "3.35.0-328",
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": "3.35.0-326",
44
- "@elementor/editor-elements": "3.35.0-326",
45
- "@elementor/editor-props": "3.35.0-326",
46
- "@elementor/editor-responsive": "3.35.0-326",
47
- "@elementor/editor-ui": "3.35.0-326",
48
- "@elementor/editor-v1-adapters": "3.35.0-326",
49
- "@elementor/env": "3.35.0-326",
50
- "@elementor/http-client": "3.35.0-326",
43
+ "@elementor/editor-current-user": "3.35.0-328",
44
+ "@elementor/editor-elements": "3.35.0-328",
45
+ "@elementor/editor-props": "3.35.0-328",
46
+ "@elementor/editor-responsive": "3.35.0-328",
47
+ "@elementor/editor-ui": "3.35.0-328",
48
+ "@elementor/editor-v1-adapters": "3.35.0-328",
49
+ "@elementor/env": "3.35.0-328",
50
+ "@elementor/http-client": "3.35.0-328",
51
51
  "@elementor/icons": "^1.61.1",
52
- "@elementor/locations": "3.35.0-326",
53
- "@elementor/mixpanel": "3.35.0-326",
54
- "@elementor/query": "3.35.0-326",
55
- "@elementor/session": "3.35.0-326",
52
+ "@elementor/locations": "3.35.0-328",
53
+ "@elementor/mixpanel": "3.35.0-328",
54
+ "@elementor/query": "3.35.0-328",
55
+ "@elementor/session": "3.35.0-328",
56
56
  "@elementor/ui": "1.36.17",
57
- "@elementor/utils": "3.35.0-326",
58
- "@elementor/wp-media": "3.35.0-326",
57
+ "@elementor/utils": "3.35.0-328",
58
+ "@elementor/wp-media": "3.35.0-328",
59
59
  "@wordpress/i18n": "^5.13.0",
60
60
  "@monaco-editor/react": "^4.7.0",
61
61
  "dayjs": "^1.11.18",
@@ -68,6 +68,7 @@
68
68
  "@tiptap/extension-superscript": "^3.11.1",
69
69
  "@tiptap/extension-text": "^3.11.1",
70
70
  "@tiptap/extension-underline": "^3.11.1",
71
+ "@tiptap/extension-link": "^3.11.1",
71
72
  "@tiptap/pm": "^3.11.1",
72
73
  "@tiptap/react": "^3.11.1",
73
74
  "@tiptap/starter-kit": "^3.11.1"
@@ -1,18 +1,21 @@
1
1
  import * as React from 'react';
2
- import { useMemo } from 'react';
2
+ import { useMemo, useRef, useState } from 'react';
3
3
  import {
4
4
  BoldIcon,
5
5
  ItalicIcon,
6
+ LinkIcon,
6
7
  MinusIcon,
7
8
  StrikethroughIcon,
8
9
  SubscriptIcon,
9
10
  SuperscriptIcon,
10
11
  UnderlineIcon,
11
12
  } from '@elementor/icons';
12
- import { Box, IconButton, ToggleButton, ToggleButtonGroup, Tooltip } from '@elementor/ui';
13
+ import { Box, IconButton, ToggleButton, ToggleButtonGroup, Tooltip, usePopupState } from '@elementor/ui';
13
14
  import { type Editor, useEditorState } from '@tiptap/react';
14
15
  import { __ } from '@wordpress/i18n';
15
16
 
17
+ import { UrlPopover } from './url-popover';
18
+
16
19
  type InlineEditorToolbarProps = {
17
20
  editor: Editor;
18
21
  };
@@ -74,6 +77,12 @@ const toolbarButtons = {
74
77
  editor.chain().focus().toggleSubscript().run();
75
78
  },
76
79
  },
80
+ link: {
81
+ label: __( 'Link', 'elementor' ),
82
+ icon: <LinkIcon fontSize="tiny" />,
83
+ action: 'link',
84
+ method: null,
85
+ },
77
86
  } as const;
78
87
 
79
88
  type ToolbarButtonKeys = keyof typeof toolbarButtons;
@@ -85,6 +94,11 @@ const { clear: clearButton, ...formatButtons } = toolbarButtons;
85
94
  const possibleFormats: FormatAction[] = Object.keys( formatButtons ) as FormatAction[];
86
95
 
87
96
  export const InlineEditorToolbar = ( { editor }: InlineEditorToolbarProps ) => {
97
+ const [ urlValue, setUrlValue ] = useState( '' );
98
+ const [ openInNewTab, setOpenInNewTab ] = useState( false );
99
+ const toolbarRef = useRef< HTMLDivElement >( null );
100
+ const popupState = usePopupState( { variant: 'popover' } );
101
+
88
102
  const editorState = useEditorState( {
89
103
  editor,
90
104
  selector: ( ctx ) => possibleFormats.filter( ( format ) => ctx.editor.isActive( format ) ),
@@ -92,8 +106,40 @@ export const InlineEditorToolbar = ( { editor }: InlineEditorToolbarProps ) => {
92
106
 
93
107
  const formatButtonsList = useMemo( () => Object.values( formatButtons ), [] );
94
108
 
109
+ const handleLinkClick = () => {
110
+ const linkAttrs = editor.getAttributes( 'link' );
111
+ setUrlValue( linkAttrs.href || '' );
112
+ setOpenInNewTab( linkAttrs.target === '_blank' );
113
+ popupState.open( toolbarRef.current );
114
+ };
115
+
116
+ const handleUrlChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
117
+ setUrlValue( event.target.value );
118
+ };
119
+
120
+ const handleToggleNewTab = () => {
121
+ setOpenInNewTab( ! openInNewTab );
122
+ };
123
+
124
+ const handleUrlSubmit = () => {
125
+ if ( urlValue ) {
126
+ editor
127
+ .chain()
128
+ .focus()
129
+ .setLink( {
130
+ href: urlValue,
131
+ target: openInNewTab ? '_blank' : '_self',
132
+ } )
133
+ .run();
134
+ } else {
135
+ editor.chain().focus().unsetLink().run();
136
+ }
137
+ popupState.close();
138
+ };
139
+
95
140
  return (
96
141
  <Box
142
+ ref={ toolbarRef }
97
143
  sx={ {
98
144
  position: 'absolute',
99
145
  top: -40,
@@ -104,6 +150,7 @@ export const InlineEditorToolbar = ( { editor }: InlineEditorToolbarProps ) => {
104
150
  backgroundColor: 'background.paper',
105
151
  boxShadow: '0 2px 8px rgba(0, 0, 0, 0.2)',
106
152
  alignItems: 'center',
153
+ visibility: popupState.isOpen ? 'hidden' : 'visible',
107
154
  } }
108
155
  >
109
156
  <Tooltip title={ clearButton.label } placement="top">
@@ -125,13 +172,24 @@ export const InlineEditorToolbar = ( { editor }: InlineEditorToolbarProps ) => {
125
172
  value={ button.action }
126
173
  aria-label={ button.label }
127
174
  size="tiny"
128
- onClick={ () => button.method( editor ) }
175
+ onClick={ () =>
176
+ button.action === 'link' ? handleLinkClick() : button.method?.( editor )
177
+ }
129
178
  >
130
179
  { button.icon }
131
180
  </ToggleButton>
132
181
  </Tooltip>
133
182
  ) ) }
134
183
  </ToggleButtonGroup>
184
+ <UrlPopover
185
+ popupState={ popupState }
186
+ anchorRef={ toolbarRef }
187
+ restoreValue={ handleUrlSubmit }
188
+ value={ urlValue }
189
+ onChange={ handleUrlChange }
190
+ openInNewTab={ openInNewTab }
191
+ onToggleNewTab={ handleToggleNewTab }
192
+ />
135
193
  </Box>
136
194
  );
137
195
  };
@@ -5,6 +5,7 @@ import Bold from '@tiptap/extension-bold';
5
5
  import Document from '@tiptap/extension-document';
6
6
  import HardBreak from '@tiptap/extension-hard-break';
7
7
  import Italic from '@tiptap/extension-italic';
8
+ import Link from '@tiptap/extension-link';
8
9
  import Strike from '@tiptap/extension-strike';
9
10
  import Subscript from '@tiptap/extension-subscript';
10
11
  import Superscript from '@tiptap/extension-superscript';
@@ -52,6 +53,9 @@ export const InlineEditor = React.forwardRef(
52
53
  Underline,
53
54
  Superscript,
54
55
  Subscript,
56
+ Link.configure( {
57
+ openOnClick: false,
58
+ } ),
55
59
  HardBreak.extend( {
56
60
  addKeyboardShortcuts() {
57
61
  return {
@@ -98,6 +102,9 @@ export const InlineEditor = React.forwardRef(
98
102
  '& .ProseMirror': {
99
103
  minHeight: '70px',
100
104
  fontSize: '12px',
105
+ '& a': {
106
+ color: 'inherit',
107
+ },
101
108
  },
102
109
  ...sx,
103
110
  } }
@@ -0,0 +1,73 @@
1
+ import * as React from 'react';
2
+ import { type RefObject, useEffect, useRef } from 'react';
3
+ import { ExternalLinkIcon } from '@elementor/icons';
4
+ import { bindPopover, Popover, type PopupState, Stack, TextField, ToggleButton } from '@elementor/ui';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ type Props = {
8
+ popupState: PopupState;
9
+ anchorRef: RefObject< HTMLDivElement | null >;
10
+ restoreValue: () => void;
11
+ value: string;
12
+ onChange: ( event: React.ChangeEvent< HTMLInputElement > ) => void;
13
+ openInNewTab: boolean;
14
+ onToggleNewTab: () => void;
15
+ };
16
+
17
+ export const UrlPopover = ( {
18
+ popupState,
19
+ restoreValue,
20
+ anchorRef,
21
+ value,
22
+ onChange,
23
+ openInNewTab,
24
+ onToggleNewTab,
25
+ }: Props ) => {
26
+ const inputRef = useRef< HTMLInputElement >( null );
27
+
28
+ useEffect( () => {
29
+ if ( popupState.isOpen ) {
30
+ requestAnimationFrame( () => inputRef.current?.focus() );
31
+ }
32
+ }, [ popupState.isOpen ] );
33
+
34
+ const handleClose = () => {
35
+ restoreValue();
36
+ popupState.close();
37
+ };
38
+
39
+ return (
40
+ <Popover
41
+ slotProps={ {
42
+ paper: { sx: { borderRadius: '16px', width: anchorRef.current?.offsetWidth + 'px', marginTop: -1 } },
43
+ } }
44
+ { ...bindPopover( popupState ) }
45
+ anchorOrigin={ { vertical: 'top', horizontal: 'left' } }
46
+ transformOrigin={ { vertical: 'top', horizontal: 'left' } }
47
+ onClose={ handleClose }
48
+ >
49
+ <Stack direction="row" alignItems="center" gap={ 1 } sx={ { p: 1.5 } }>
50
+ <TextField
51
+ value={ value }
52
+ onChange={ onChange }
53
+ size="tiny"
54
+ fullWidth
55
+ placeholder={ __( 'Type a URL', 'elementor' ) }
56
+ inputProps={ { ref: inputRef } }
57
+ color="secondary"
58
+ InputProps={ { sx: { borderRadius: '8px' } } }
59
+ />
60
+ <ToggleButton
61
+ size="tiny"
62
+ value="newTab"
63
+ selected={ openInNewTab }
64
+ onClick={ onToggleNewTab }
65
+ aria-label={ __( 'Open in a new tab', 'elementor' ) }
66
+ sx={ { borderRadius: '8px' } }
67
+ >
68
+ <ExternalLinkIcon fontSize="tiny" />
69
+ </ToggleButton>
70
+ </Stack>
71
+ </Popover>
72
+ );
73
+ };