@elementor/editor-controls 3.33.0-99 → 3.35.0-325

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 (94) hide show
  1. package/dist/index.d.mts +276 -85
  2. package/dist/index.d.ts +276 -85
  3. package/dist/index.js +2491 -1783
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2304 -1592
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +31 -17
  8. package/src/bound-prop-context/prop-context.tsx +7 -1
  9. package/src/bound-prop-context/use-bound-prop.ts +19 -5
  10. package/src/components/autocomplete.tsx +34 -3
  11. package/src/components/conditional-control-infotip.tsx +64 -0
  12. package/src/components/{unstable-repeater → control-repeater}/actions/disable-item-action.tsx +2 -2
  13. package/src/components/{unstable-repeater → control-repeater}/actions/duplicate-item-action.tsx +10 -4
  14. package/src/components/{unstable-repeater → control-repeater}/actions/remove-item-action.tsx +2 -2
  15. package/src/components/control-repeater/context/item-context.tsx +8 -0
  16. package/src/components/{unstable-repeater → control-repeater}/context/repeater-context.tsx +24 -15
  17. package/src/components/control-repeater/control-repeater.tsx +29 -0
  18. package/src/components/{unstable-repeater → control-repeater}/index.ts +1 -2
  19. package/src/components/{unstable-repeater → control-repeater}/items/edit-item-popover.tsx +6 -20
  20. package/src/components/control-repeater/items/item.tsx +75 -0
  21. package/src/components/{unstable-repeater → control-repeater}/items/items-container.tsx +8 -13
  22. package/src/components/{unstable-repeater → control-repeater}/locations.ts +0 -4
  23. package/src/components/{unstable-repeater → control-repeater}/types.ts +1 -2
  24. package/src/components/control-toggle-button-group.tsx +79 -69
  25. package/src/components/enable-unfiltered-modal.tsx +1 -26
  26. package/src/components/icon-buttons/clear-icon-button.tsx +23 -0
  27. package/src/components/inline-editor-toolbar.tsx +137 -0
  28. package/src/components/inline-editor.tsx +111 -0
  29. package/src/components/item-selector.tsx +10 -4
  30. package/src/components/{unstable-repeater/header/header.tsx → repeater/repeater-header.tsx} +4 -12
  31. package/src/components/repeater/repeater-popover.tsx +19 -0
  32. package/src/components/repeater/repeater-tag.tsx +16 -0
  33. package/src/components/repeater/repeater.tsx +405 -0
  34. package/src/components/{sortable.tsx → repeater/sortable.tsx} +1 -1
  35. package/src/components/size-control/size-input.tsx +20 -14
  36. package/src/components/size-control/text-field-inner-selection.tsx +15 -2
  37. package/src/control-adornments/control-adornments-context.tsx +5 -4
  38. package/src/control-replacements.tsx +12 -47
  39. package/src/controls/background-control/background-control.tsx +43 -12
  40. package/src/controls/background-control/background-gradient-color-control.tsx +5 -8
  41. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +18 -13
  42. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +25 -16
  43. package/src/controls/box-shadow-repeater-control.tsx +38 -21
  44. package/src/controls/color-control.tsx +3 -1
  45. package/src/controls/date-time-control.tsx +108 -0
  46. package/src/controls/filter-control/drop-shadow/drop-shadow-item-content.tsx +1 -0
  47. package/src/controls/filter-control/drop-shadow/drop-shadow-item-label.tsx +10 -6
  48. package/src/controls/filter-control/filter-content.tsx +1 -1
  49. package/src/controls/filter-control/filter-repeater-control.tsx +24 -21
  50. package/src/controls/filter-control/single-size/single-size-item-content.tsx +1 -1
  51. package/src/controls/filter-control/single-size/single-size-item-label.tsx +2 -1
  52. package/src/controls/font-family-control/font-family-control.tsx +66 -55
  53. package/src/controls/html-tag-control.tsx +90 -0
  54. package/src/controls/image-media-control.tsx +2 -2
  55. package/src/controls/inline-editing-control.tsx +18 -0
  56. package/src/controls/key-value-control.tsx +8 -2
  57. package/src/controls/link-control.tsx +23 -123
  58. package/src/controls/query-control.tsx +168 -0
  59. package/src/controls/repeatable-control.tsx +62 -27
  60. package/src/controls/select-control-wrapper.tsx +57 -0
  61. package/src/controls/select-control.tsx +9 -5
  62. package/src/controls/selection-size-control.tsx +13 -2
  63. package/src/controls/size-control.tsx +43 -25
  64. package/src/controls/svg-media-control.tsx +33 -10
  65. package/src/controls/text-area-control.tsx +5 -1
  66. package/src/controls/text-control.tsx +5 -0
  67. package/src/controls/toggle-control.tsx +11 -2
  68. package/src/controls/transform-control/functions/axis-row.tsx +1 -0
  69. package/src/controls/transform-control/transform-icon.tsx +2 -2
  70. package/src/controls/transform-control/transform-label.tsx +15 -32
  71. package/src/controls/transform-control/transform-repeater-control.tsx +42 -36
  72. package/src/controls/transform-control/{transform-base-control.tsx → transform-settings-control.tsx} +2 -2
  73. package/src/controls/transform-control/use-transform-tabs-history.tsx +1 -1
  74. package/src/controls/transition-control/data.ts +16 -1
  75. package/src/controls/transition-control/trainsition-events.ts +2 -2
  76. package/src/controls/transition-control/transition-repeater-control.tsx +137 -13
  77. package/src/controls/transition-control/transition-selector.tsx +37 -14
  78. package/src/controls/url-control.tsx +21 -16
  79. package/src/create-control.tsx +3 -2
  80. package/src/hooks/use-filtered-items-list.ts +3 -2
  81. package/src/hooks/use-repeatable-control-context.ts +3 -0
  82. package/src/hooks/use-sync-external-state.tsx +0 -1
  83. package/src/index.ts +21 -5
  84. package/src/utils/convert-toggle-options-to-atomic.tsx +33 -0
  85. package/src/utils/escape-html-attr.ts +11 -0
  86. package/src/components/css-code-editor/css-editor.styles.ts +0 -52
  87. package/src/components/css-code-editor/css-editor.tsx +0 -142
  88. package/src/components/css-code-editor/css-validation.ts +0 -75
  89. package/src/components/css-code-editor/resize-handle.tsx +0 -55
  90. package/src/components/css-code-editor/visual-content-change-protection.ts +0 -69
  91. package/src/components/repeater.tsx +0 -343
  92. package/src/components/unstable-repeater/items/item.tsx +0 -77
  93. package/src/components/unstable-repeater/unstable-repeater.tsx +0 -26
  94. /package/src/components/{unstable-repeater → control-repeater}/actions/tooltip-add-item-action.tsx +0 -0
@@ -1,18 +1,20 @@
1
1
  import * as React from 'react';
2
2
 
3
- import { SortableItem, SortableProvider } from '../../sortable';
3
+ import { SortableItem, SortableProvider } from '../../repeater/sortable';
4
+ import { ItemContext } from '../context/item-context';
4
5
  import { useRepeaterContext } from '../context/repeater-context';
5
- import { type Item, type ItemProps, type RepeatablePropValue } from '../types';
6
+ import { type Item, type RepeatablePropValue } from '../types';
6
7
 
7
8
  export const ItemsContainer = < T extends RepeatablePropValue >( {
8
- itemTemplate,
9
9
  isSortable = true,
10
10
  children,
11
- }: React.PropsWithChildren< { itemTemplate: React.ReactNode; isSortable?: boolean } > ) => {
11
+ }: React.PropsWithChildren< {
12
+ isSortable?: boolean;
13
+ } > ) => {
12
14
  const { items, setItems } = useRepeaterContext();
13
15
  const keys = items.map( ( { key } ) => key );
14
16
 
15
- if ( ! itemTemplate ) {
17
+ if ( ! children ) {
16
18
  return null;
17
19
  }
18
20
 
@@ -34,14 +36,7 @@ export const ItemsContainer = < T extends RepeatablePropValue >( {
34
36
 
35
37
  return (
36
38
  <SortableItem id={ key } key={ `sortable-${ key }` } disabled={ ! isSortable }>
37
- { React.isValidElement< React.PropsWithChildren< ItemProps< T > > >( itemTemplate )
38
- ? React.cloneElement( itemTemplate, {
39
- key,
40
- value,
41
- index,
42
- children,
43
- } )
44
- : null }
39
+ <ItemContext.Provider value={ { index, value } }>{ children }</ItemContext.Provider>
45
40
  </SortableItem>
46
41
  );
47
42
  } ) }
@@ -10,10 +10,6 @@ export const { Slot: RepeaterItemLabelSlot, inject: injectIntoRepeaterItemLabel
10
10
  value: PropValue;
11
11
  } >();
12
12
 
13
- export const { Slot: RepeaterHeaderActionsSlot, inject: injectIntoRepeaterHeaderActions } = createLocation< {
14
- value: PropValue;
15
- } >();
16
-
17
13
  export const { Slot: RepeaterItemActionsSlot, inject: injectIntoRepeaterItemActions } = createLocation< {
18
14
  index: number;
19
15
  } >();
@@ -11,6 +11,5 @@ export type RepeatablePropValue = TransformablePropValue< PropKey, PropValue >;
11
11
  export type ItemProps< T > = {
12
12
  Label: React.ComponentType< { value: T } >;
13
13
  Icon: React.ComponentType< { value: T } >;
14
- value?: Item< T >;
15
- index?: number;
14
+ actions?: React.ReactNode;
16
15
  };
@@ -80,76 +80,79 @@ type Props< TValue > = {
80
80
  onChange: ( value: ExclusiveValue< TValue > ) => void;
81
81
  }
82
82
  );
83
-
84
- export const ControlToggleButtonGroup = < TValue, >( {
85
- justify = 'end',
86
- size = 'tiny',
87
- value,
88
- onChange,
89
- items,
90
- maxItems,
91
- exclusive = false,
92
- fullWidth = false,
93
- disabled,
94
- placeholder,
95
- }: Props< TValue > ) => {
96
- const shouldSliceItems = exclusive && maxItems !== undefined && items.length > maxItems;
97
- const menuItems = shouldSliceItems ? items.slice( maxItems - 1 ) : [];
98
- const fixedItems = shouldSliceItems ? items.slice( 0, maxItems - 1 ) : items;
99
-
100
- const theme = useTheme();
101
- const isRtl = 'rtl' === theme.direction;
102
-
103
- const handleChange = (
104
- _: React.MouseEvent< HTMLElement >,
105
- newValue: typeof exclusive extends true ? ExclusiveValue< TValue > : NonExclusiveValue< TValue >
83
+ export const ToggleButtonGroupUi = React.forwardRef(
84
+ < TValue, >(
85
+ {
86
+ justify = 'end',
87
+ size = 'tiny',
88
+ value,
89
+ onChange,
90
+ items,
91
+ maxItems,
92
+ exclusive = false,
93
+ fullWidth = false,
94
+ disabled,
95
+ placeholder,
96
+ }: Props< TValue >,
97
+ ref: React.Ref< HTMLDivElement >
106
98
  ) => {
107
- onChange( newValue as never );
108
- };
109
-
110
- const getGridTemplateColumns = useMemo( () => {
111
- const isOffLimits = menuItems?.length;
112
- const itemsCount = isOffLimits ? fixedItems.length + 1 : fixedItems.length;
113
- const templateColumnsSuffix = isOffLimits ? 'auto' : '';
114
- return `repeat(${ itemsCount }, minmax(0, 25%)) ${ templateColumnsSuffix }`;
115
- }, [ menuItems?.length, fixedItems.length ] );
116
-
117
- const shouldShowExclusivePlaceholder = exclusive && ( value === null || value === undefined || value === '' );
118
-
119
- const nonExclusiveSelectedValues =
120
- ! exclusive && Array.isArray( value )
121
- ? value
122
- .map( ( v ) => ( typeof v === 'string' ? v : '' ) )
123
- .join( ' ' )
124
- .trim()
125
- .split( /\s+/ )
126
- .filter( Boolean )
127
- : [];
128
-
129
- const shouldShowNonExclusivePlaceholder = ! exclusive && nonExclusiveSelectedValues.length === 0;
130
-
131
- const getPlaceholderArray = ( placeholderValue: TValue | TValue[] | undefined ): string[] => {
132
- if ( Array.isArray( placeholderValue ) ) {
133
- return placeholderValue.flatMap( ( p ) => {
134
- if ( typeof p === 'string' ) {
135
- return p.trim().split( /\s+/ ).filter( Boolean );
136
- }
137
- return [];
138
- } );
139
- }
140
-
141
- if ( typeof placeholderValue === 'string' ) {
142
- return placeholderValue.trim().split( /\s+/ ).filter( Boolean );
143
- }
144
-
145
- return [];
146
- };
147
-
148
- const placeholderArray = getPlaceholderArray( placeholder );
149
-
150
- return (
151
- <ControlActions>
99
+ const shouldSliceItems = exclusive && maxItems !== undefined && items.length > maxItems;
100
+ const menuItems = shouldSliceItems ? items.slice( maxItems - 1 ) : [];
101
+ const fixedItems = shouldSliceItems ? items.slice( 0, maxItems - 1 ) : items;
102
+
103
+ const theme = useTheme();
104
+ const isRtl = 'rtl' === theme.direction;
105
+
106
+ const handleChange = (
107
+ _: React.MouseEvent< HTMLElement >,
108
+ newValue: typeof exclusive extends true ? ExclusiveValue< TValue > : NonExclusiveValue< TValue >
109
+ ) => {
110
+ onChange( newValue as never );
111
+ };
112
+
113
+ const getGridTemplateColumns = useMemo( () => {
114
+ const isOffLimits = menuItems?.length;
115
+ const itemsCount = isOffLimits ? fixedItems.length + 1 : fixedItems.length;
116
+ const templateColumnsSuffix = isOffLimits ? 'auto' : '';
117
+ return `repeat(${ itemsCount }, minmax(0, 25%)) ${ templateColumnsSuffix }`;
118
+ }, [ menuItems?.length, fixedItems.length ] );
119
+
120
+ const shouldShowExclusivePlaceholder = exclusive && ( value === null || value === undefined || value === '' );
121
+
122
+ const nonExclusiveSelectedValues =
123
+ ! exclusive && Array.isArray( value )
124
+ ? value
125
+ .map( ( v ) => ( typeof v === 'string' ? v : '' ) )
126
+ .join( ' ' )
127
+ .trim()
128
+ .split( /\s+/ )
129
+ .filter( Boolean )
130
+ : [];
131
+
132
+ const shouldShowNonExclusivePlaceholder = ! exclusive && nonExclusiveSelectedValues.length === 0;
133
+
134
+ const getPlaceholderArray = ( placeholderValue: TValue | TValue[] | undefined ): string[] => {
135
+ if ( Array.isArray( placeholderValue ) ) {
136
+ return placeholderValue.flatMap( ( p ) => {
137
+ if ( typeof p === 'string' ) {
138
+ return p.trim().split( /\s+/ ).filter( Boolean );
139
+ }
140
+ return [];
141
+ } );
142
+ }
143
+
144
+ if ( typeof placeholderValue === 'string' ) {
145
+ return placeholderValue.trim().split( /\s+/ ).filter( Boolean );
146
+ }
147
+
148
+ return [];
149
+ };
150
+
151
+ const placeholderArray = getPlaceholderArray( placeholder );
152
+
153
+ return (
152
154
  <StyledToggleButtonGroup
155
+ ref={ ref }
153
156
  justify={ justify }
154
157
  value={ value }
155
158
  onChange={ handleChange }
@@ -197,6 +200,14 @@ export const ControlToggleButtonGroup = < TValue, >( {
197
200
  />
198
201
  ) }
199
202
  </StyledToggleButtonGroup>
203
+ );
204
+ }
205
+ ) as < TValue >( props: Props< TValue > & { ref?: React.Ref< HTMLDivElement > } ) => React.ReactElement;
206
+
207
+ export const ControlToggleButtonGroup = < TValue, >( props: Props< TValue > ) => {
208
+ return (
209
+ <ControlActions>
210
+ <ToggleButtonGroupUi { ...props } />
200
211
  </ControlActions>
201
212
  );
202
213
  };
@@ -247,7 +258,6 @@ const SplitButtonGroup = < TValue, >( {
247
258
  ev.preventDefault();
248
259
  onMenuItemClick( previewButton.value );
249
260
  } }
250
- ref={ menuButtonRef }
251
261
  >
252
262
  { previewButton.renderContent( { size } ) }
253
263
  </ToggleButton>
@@ -1,6 +1,5 @@
1
1
  import * as React from 'react';
2
2
  import { useState } from 'react';
3
- import { useCurrentUserCapabilities } from '@elementor/editor-current-user';
4
3
  import {
5
4
  Button,
6
5
  CircularProgress,
@@ -34,11 +33,6 @@ const ADMIN_CONTENT_TEXT = __(
34
33
  'Before you enable unfiltered files upload, note that such files include a security risk. Elementor does run a process to remove possible malicious code, but there is still risk involved when using such files.',
35
34
  'elementor'
36
35
  );
37
- const NON_ADMIN_TITLE_TEXT = __( "Sorry, you can't upload that file yet", 'elementor' );
38
- const NON_ADMIN_CONTENT_TEXT = __(
39
- 'This is because this file type may pose a security risk. To upload them anyway, ask the site administrator to enable unfiltered file uploads.',
40
- 'elementor'
41
- );
42
36
 
43
37
  const ADMIN_FAILED_CONTENT_TEXT_PT1 = __( 'Failed to enable unfiltered files upload.', 'elementor' );
44
38
 
@@ -51,9 +45,7 @@ const WAIT_FOR_CLOSE_TIMEOUT_MS = 300;
51
45
 
52
46
  export const EnableUnfilteredModal = ( props: EnableUnfilteredModalProps ) => {
53
47
  const { mutateAsync, isPending } = useUpdateUnfilteredFilesUpload();
54
- const { canUser } = useCurrentUserCapabilities();
55
48
  const [ isError, setIsError ] = useState( false );
56
- const canManageOptions = canUser( 'manage_options' );
57
49
 
58
50
  const onClose = ( enabled: boolean ) => {
59
51
  props.onClose( enabled );
@@ -75,7 +67,7 @@ export const EnableUnfilteredModal = ( props: EnableUnfilteredModalProps ) => {
75
67
 
76
68
  const dialogProps = { ...props, isPending, handleEnable, isError, onClose };
77
69
 
78
- return canManageOptions ? <AdminDialog { ...dialogProps } /> : <NonAdminDialog { ...dialogProps } />;
70
+ return <AdminDialog { ...dialogProps } />;
79
71
  };
80
72
 
81
73
  const AdminDialog = ( { open, onClose, handleEnable, isPending, isError }: LocalModalProps ) => (
@@ -111,20 +103,3 @@ const AdminDialog = ( { open, onClose, handleEnable, isPending, isError }: Local
111
103
  </DialogActions>
112
104
  </Dialog>
113
105
  );
114
-
115
- const NonAdminDialog = ( { open, onClose }: LocalModalProps ) => (
116
- <Dialog open={ open } maxWidth={ 'sm' } onClose={ () => onClose( false ) }>
117
- <DialogHeader logo={ false }>
118
- <DialogTitle>{ NON_ADMIN_TITLE_TEXT }</DialogTitle>
119
- </DialogHeader>
120
- <Divider />
121
- <DialogContent>
122
- <DialogContentText>{ NON_ADMIN_CONTENT_TEXT }</DialogContentText>
123
- </DialogContent>
124
- <DialogActions>
125
- <Button size={ 'medium' } onClick={ () => onClose( false ) } variant="contained" color="primary">
126
- { __( 'Got it', 'elementor' ) }
127
- </Button>
128
- </DialogActions>
129
- </Dialog>
130
- );
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+ import { BrushBigIcon } from '@elementor/icons';
3
+ import { IconButton, styled, Tooltip } from '@elementor/ui';
4
+
5
+ type ClearIconButtonProps = {
6
+ onClick?: () => void;
7
+ tooltipText: React.ReactNode;
8
+ disabled?: boolean;
9
+ size?: 'tiny' | 'small' | 'medium' | 'large';
10
+ };
11
+
12
+ const CustomIconButton = styled( IconButton )( ( { theme } ) => ( {
13
+ width: theme.spacing( 2.5 ),
14
+ height: theme.spacing( 2.5 ),
15
+ } ) );
16
+
17
+ export const ClearIconButton = ( { tooltipText, onClick, disabled, size = 'tiny' }: ClearIconButtonProps ) => (
18
+ <Tooltip title={ tooltipText } placement="top" disableInteractive>
19
+ <CustomIconButton aria-label={ tooltipText } size={ size } onClick={ onClick } disabled={ disabled }>
20
+ <BrushBigIcon fontSize={ size } />
21
+ </CustomIconButton>
22
+ </Tooltip>
23
+ );
@@ -0,0 +1,137 @@
1
+ import * as React from 'react';
2
+ import { useMemo } from 'react';
3
+ import {
4
+ BoldIcon,
5
+ ItalicIcon,
6
+ MinusIcon,
7
+ StrikethroughIcon,
8
+ SubscriptIcon,
9
+ SuperscriptIcon,
10
+ UnderlineIcon,
11
+ } from '@elementor/icons';
12
+ import { Box, IconButton, ToggleButton, ToggleButtonGroup, Tooltip } from '@elementor/ui';
13
+ import { type Editor, useEditorState } from '@tiptap/react';
14
+ import { __ } from '@wordpress/i18n';
15
+
16
+ type InlineEditorToolbarProps = {
17
+ editor: Editor;
18
+ };
19
+
20
+ const toolbarButtons = {
21
+ clear: {
22
+ label: __( 'Clear', 'elementor' ),
23
+ icon: <MinusIcon fontSize="tiny" />,
24
+ action: 'clear',
25
+ method: ( editor: Editor ) => {
26
+ editor.chain().focus().clearNodes().unsetAllMarks().run();
27
+ },
28
+ },
29
+ bold: {
30
+ label: __( 'Bold', 'elementor' ),
31
+ icon: <BoldIcon fontSize="tiny" />,
32
+ action: 'bold',
33
+ method: ( editor: Editor ) => {
34
+ editor.chain().focus().toggleBold().run();
35
+ },
36
+ },
37
+ italic: {
38
+ label: __( 'Italic', 'elementor' ),
39
+ icon: <ItalicIcon fontSize="tiny" />,
40
+ action: 'italic',
41
+ method: ( editor: Editor ) => {
42
+ editor.chain().focus().toggleItalic().run();
43
+ },
44
+ },
45
+ underline: {
46
+ label: __( 'Underline', 'elementor' ),
47
+ icon: <UnderlineIcon fontSize="tiny" />,
48
+ action: 'underline',
49
+ method: ( editor: Editor ) => {
50
+ editor.chain().focus().toggleUnderline().run();
51
+ },
52
+ },
53
+ strike: {
54
+ label: __( 'Strikethrough', 'elementor' ),
55
+ icon: <StrikethroughIcon fontSize="tiny" />,
56
+ action: 'strike',
57
+ method: ( editor: Editor ) => {
58
+ editor.chain().focus().toggleStrike().run();
59
+ },
60
+ },
61
+ superscript: {
62
+ label: __( 'Superscript', 'elementor' ),
63
+ icon: <SuperscriptIcon fontSize="tiny" />,
64
+ action: 'superscript',
65
+ method: ( editor: Editor ) => {
66
+ editor.chain().focus().toggleSuperscript().run();
67
+ },
68
+ },
69
+ subscript: {
70
+ label: __( 'Subscript', 'elementor' ),
71
+ icon: <SubscriptIcon fontSize="tiny" />,
72
+ action: 'subscript',
73
+ method: ( editor: Editor ) => {
74
+ editor.chain().focus().toggleSubscript().run();
75
+ },
76
+ },
77
+ } as const;
78
+
79
+ type ToolbarButtonKeys = keyof typeof toolbarButtons;
80
+
81
+ type FormatAction = Omit< ToolbarButtonKeys, 'clear' >;
82
+
83
+ const { clear: clearButton, ...formatButtons } = toolbarButtons;
84
+
85
+ const possibleFormats: FormatAction[] = Object.keys( formatButtons ) as FormatAction[];
86
+
87
+ export const InlineEditorToolbar = ( { editor }: InlineEditorToolbarProps ) => {
88
+ const editorState = useEditorState( {
89
+ editor,
90
+ selector: ( ctx ) => possibleFormats.filter( ( format ) => ctx.editor.isActive( format ) ),
91
+ } );
92
+
93
+ const formatButtonsList = useMemo( () => Object.values( formatButtons ), [] );
94
+
95
+ return (
96
+ <Box
97
+ sx={ {
98
+ position: 'absolute',
99
+ top: -40,
100
+ display: 'inline-flex',
101
+ gap: 0.5,
102
+ padding: 0.5,
103
+ borderRadius: 1,
104
+ backgroundColor: 'background.paper',
105
+ boxShadow: '0 2px 8px rgba(0, 0, 0, 0.2)',
106
+ alignItems: 'center',
107
+ } }
108
+ >
109
+ <Tooltip title={ clearButton.label } placement="top">
110
+ <IconButton aria-label={ clearButton.label } onClick={ () => clearButton.method( editor ) } size="tiny">
111
+ { clearButton.icon }
112
+ </IconButton>
113
+ </Tooltip>
114
+ <ToggleButtonGroup
115
+ value={ editorState }
116
+ size="tiny"
117
+ sx={ {
118
+ display: 'flex',
119
+ gap: 0.5,
120
+ } }
121
+ >
122
+ { formatButtonsList.map( ( button ) => (
123
+ <Tooltip title={ button.label } key={ button.action } placement="top">
124
+ <ToggleButton
125
+ value={ button.action }
126
+ aria-label={ button.label }
127
+ size="tiny"
128
+ onClick={ () => button.method( editor ) }
129
+ >
130
+ { button.icon }
131
+ </ToggleButton>
132
+ </Tooltip>
133
+ ) ) }
134
+ </ToggleButtonGroup>
135
+ </Box>
136
+ );
137
+ };
@@ -0,0 +1,111 @@
1
+ import * as React from 'react';
2
+ import { type DependencyList, type ForwardedRef, useEffect, useRef } from 'react';
3
+ import { Box, type SxProps, type Theme } from '@elementor/ui';
4
+ import Bold from '@tiptap/extension-bold';
5
+ import Document from '@tiptap/extension-document';
6
+ import HardBreak from '@tiptap/extension-hard-break';
7
+ import Italic from '@tiptap/extension-italic';
8
+ import Strike from '@tiptap/extension-strike';
9
+ import Subscript from '@tiptap/extension-subscript';
10
+ import Superscript from '@tiptap/extension-superscript';
11
+ import Text from '@tiptap/extension-text';
12
+ import Underline from '@tiptap/extension-underline';
13
+ import { type AnyExtension, EditorContent, useEditor } from '@tiptap/react';
14
+
15
+ import { InlineEditorToolbar } from './inline-editor-toolbar';
16
+
17
+ type InlineEditorProps = {
18
+ value: string;
19
+ setValue: ( value: string ) => void;
20
+ attributes?: Record< string, string >;
21
+ sx?: SxProps< Theme >;
22
+ showToolbar?: boolean;
23
+ };
24
+
25
+ const useOnUpdate = ( callback: () => void, dependencies: DependencyList ): void => {
26
+ const hasMounted = useRef( false );
27
+
28
+ useEffect( () => {
29
+ if ( hasMounted.current ) {
30
+ callback();
31
+ } else {
32
+ hasMounted.current = true;
33
+ }
34
+ // eslint-disable-next-line react-hooks/exhaustive-deps
35
+ }, dependencies );
36
+ };
37
+
38
+ export const InlineEditor = React.forwardRef(
39
+ (
40
+ { value, setValue, attributes = {}, showToolbar = false, sx }: InlineEditorProps,
41
+ ref: ForwardedRef< HTMLDivElement >
42
+ ) => {
43
+ const editor = useEditor( {
44
+ extensions: [
45
+ Document.extend( {
46
+ content: 'inline*',
47
+ } ),
48
+ Text,
49
+ Bold,
50
+ Italic,
51
+ Strike,
52
+ Underline,
53
+ Superscript,
54
+ Subscript,
55
+ HardBreak.extend( {
56
+ addKeyboardShortcuts() {
57
+ return {
58
+ Enter: () => this.editor.commands.setHardBreak(),
59
+ };
60
+ },
61
+ } ),
62
+ ] as AnyExtension[],
63
+ content: value,
64
+ onUpdate: ( { editor: updatedEditor } ) => setValue( updatedEditor.getHTML() ),
65
+ } );
66
+
67
+ useOnUpdate( () => {
68
+ if ( ! editor ) {
69
+ return;
70
+ }
71
+
72
+ const currentContent = editor.getHTML();
73
+
74
+ if ( currentContent !== value ) {
75
+ editor.commands.setContent( value, { emitUpdate: false } );
76
+ }
77
+ }, [ editor, value ] );
78
+
79
+ return (
80
+ <Box
81
+ ref={ ref }
82
+ sx={ {
83
+ p: 0.8,
84
+ border: '1px solid',
85
+ borderColor: 'grey.200',
86
+ borderRadius: '8px',
87
+ transition: 'border-color .2s ease, box-shadow .2s ease',
88
+ '&:hover': {
89
+ borderColor: 'black',
90
+ },
91
+ '&:focus-within': {
92
+ borderColor: 'black',
93
+ boxShadow: '0 0 0 1px black',
94
+ },
95
+ '& .ProseMirror:focus': {
96
+ outline: 'none',
97
+ },
98
+ '& .ProseMirror': {
99
+ minHeight: '70px',
100
+ fontSize: '12px',
101
+ },
102
+ ...sx,
103
+ } }
104
+ { ...attributes }
105
+ >
106
+ { showToolbar && <InlineEditorToolbar editor={ editor } /> }
107
+ <EditorContent editor={ editor } />
108
+ </Box>
109
+ );
110
+ }
111
+ );
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { useCallback, useEffect, useState } from 'react';
3
- import { PopoverBody, PopoverHeader, PopoverMenuList, PopoverSearch } from '@elementor/editor-ui';
3
+ import { PopoverBody, PopoverHeader, PopoverMenuList, SearchField } from '@elementor/editor-ui';
4
4
  import { Box, Divider, Link, Stack, Typography } from '@elementor/ui';
5
5
  import { debounce } from '@elementor/utils';
6
6
  import { __ } from '@wordpress/i18n';
@@ -22,6 +22,8 @@ type ItemSelectorProps = {
22
22
  itemStyle?: ( item: SelectableItem ) => React.CSSProperties;
23
23
  onDebounce?: ( name: string ) => void;
24
24
  icon: React.ElementType< { fontSize: string } >;
25
+ disabledItems?: string[];
26
+ id?: string;
25
27
  };
26
28
 
27
29
  export const ItemSelector = ( {
@@ -34,10 +36,12 @@ export const ItemSelector = ( {
34
36
  itemStyle = () => ( {} ),
35
37
  onDebounce = () => {},
36
38
  icon,
39
+ disabledItems,
40
+ id = 'item-selector',
37
41
  }: ItemSelectorProps ) => {
38
42
  const [ searchValue, setSearchValue ] = useState( '' );
39
43
 
40
- const filteredItemsList = useFilteredItemsList( itemsList, searchValue );
44
+ const filteredItemsList = useFilteredItemsList( itemsList, searchValue, disabledItems );
41
45
 
42
46
  const IconComponent = icon;
43
47
 
@@ -51,12 +55,13 @@ export const ItemSelector = ( {
51
55
  };
52
56
 
53
57
  return (
54
- <PopoverBody width={ sectionWidth }>
58
+ <PopoverBody width={ sectionWidth } id={ id }>
55
59
  <PopoverHeader title={ title } onClose={ handleClose } icon={ <IconComponent fontSize="tiny" /> } />
56
- <PopoverSearch
60
+ <SearchField
57
61
  value={ searchValue }
58
62
  onSearch={ handleSearch }
59
63
  placeholder={ __( 'Search', 'elementor' ) }
64
+ id={ id + '-search' }
60
65
  />
61
66
 
62
67
  <Divider />
@@ -128,6 +133,7 @@ type ItemListProps = {
128
133
  selectedItem: string | null;
129
134
  itemStyle?: ( item: SelectableItem ) => React.CSSProperties;
130
135
  onDebounce?: ( name: string ) => void;
136
+ disabledItems?: string[];
131
137
  };
132
138
 
133
139
  const ItemList = ( {
@@ -1,13 +1,10 @@
1
1
  import * as React from 'react';
2
+ import { forwardRef } from 'react';
2
3
  import { Box, Stack, Typography } from '@elementor/ui';
3
4
 
4
- import { useBoundProp } from '../../../bound-prop-context/use-bound-prop';
5
- import { ControlAdornments } from '../../../control-adornments/control-adornments';
6
- import { SlotChildren } from '../../../control-replacements';
7
- import { TooltipAddItemAction } from '../actions/tooltip-add-item-action';
8
- import { RepeaterHeaderActionsSlot } from '../locations';
5
+ import { ControlAdornments } from '../../control-adornments/control-adornments';
9
6
 
10
- export const Header = React.forwardRef(
7
+ export const RepeaterHeader = forwardRef(
11
8
  (
12
9
  {
13
10
  label,
@@ -19,8 +16,6 @@ export const Header = React.forwardRef(
19
16
  } >,
20
17
  ref
21
18
  ) => {
22
- const { value } = useBoundProp();
23
-
24
19
  return (
25
20
  <Stack
26
21
  direction="row"
@@ -35,10 +30,7 @@ export const Header = React.forwardRef(
35
30
  </Typography>
36
31
  <Adornment />
37
32
  </Box>
38
- <RepeaterHeaderActionsSlot value={ value } />
39
- <SlotChildren whitelist={ [ TooltipAddItemAction ] as React.FC[] } sorted>
40
- { children }
41
- </SlotChildren>
33
+ { children }
42
34
  </Stack>
43
35
  );
44
36
  }
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+ import { Popover, type PopoverProps } from '@elementor/ui';
3
+
4
+ export const RepeaterPopover = ( { children, width, ...props }: PopoverProps & { width?: number } ) => {
5
+ return (
6
+ <Popover
7
+ disablePortal
8
+ anchorOrigin={ { vertical: 'bottom', horizontal: 'left' } }
9
+ slotProps={ {
10
+ paper: {
11
+ sx: { marginBlockStart: 0.5, width, overflow: 'visible' },
12
+ },
13
+ } }
14
+ { ...props }
15
+ >
16
+ { children }
17
+ </Popover>
18
+ );
19
+ };