@elementor/editor-controls 3.33.0-99 → 3.34.3
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/dist/index.d.mts +264 -74
- package/dist/index.d.ts +264 -74
- package/dist/index.js +2541 -1861
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2344 -1660
- package/dist/index.mjs.map +1 -1
- package/package.json +31 -17
- package/src/bound-prop-context/prop-context.tsx +8 -1
- package/src/bound-prop-context/use-bound-prop.ts +19 -5
- package/src/components/autocomplete.tsx +34 -3
- package/src/components/conditional-control-infotip.tsx +64 -0
- package/src/components/{unstable-repeater → control-repeater}/actions/disable-item-action.tsx +2 -2
- package/src/components/{unstable-repeater → control-repeater}/actions/duplicate-item-action.tsx +10 -4
- package/src/components/{unstable-repeater → control-repeater}/actions/remove-item-action.tsx +2 -2
- package/src/components/control-repeater/context/item-context.tsx +8 -0
- package/src/components/{unstable-repeater → control-repeater}/context/repeater-context.tsx +24 -15
- package/src/components/control-repeater/control-repeater.tsx +29 -0
- package/src/components/{unstable-repeater → control-repeater}/index.ts +1 -2
- package/src/components/{unstable-repeater → control-repeater}/items/edit-item-popover.tsx +6 -20
- package/src/components/control-repeater/items/item.tsx +75 -0
- package/src/components/{unstable-repeater → control-repeater}/items/items-container.tsx +8 -13
- package/src/components/{unstable-repeater → control-repeater}/locations.ts +0 -4
- package/src/components/{unstable-repeater → control-repeater}/types.ts +1 -2
- package/src/components/control-toggle-button-group.tsx +79 -69
- package/src/components/enable-unfiltered-modal.tsx +1 -26
- package/src/components/icon-buttons/clear-icon-button.tsx +23 -0
- package/src/components/inline-editor-toolbar.tsx +137 -0
- package/src/components/inline-editor.tsx +111 -0
- package/src/components/item-selector.tsx +10 -4
- package/src/components/{unstable-repeater/header/header.tsx → repeater/repeater-header.tsx} +4 -12
- package/src/components/repeater/repeater-popover.tsx +19 -0
- package/src/components/repeater/repeater-tag.tsx +16 -0
- package/src/components/repeater/repeater.tsx +405 -0
- package/src/components/{sortable.tsx → repeater/sortable.tsx} +1 -1
- package/src/components/size-control/size-input.tsx +20 -14
- package/src/components/size-control/text-field-inner-selection.tsx +15 -2
- package/src/control-adornments/control-adornments-context.tsx +5 -4
- package/src/control-replacements.tsx +3 -43
- package/src/controls/background-control/background-control.tsx +43 -12
- package/src/controls/background-control/background-gradient-color-control.tsx +5 -8
- package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +18 -13
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +25 -16
- package/src/controls/box-shadow-repeater-control.tsx +38 -21
- package/src/controls/color-control.tsx +3 -1
- package/src/controls/date-time-control.tsx +108 -0
- package/src/controls/filter-control/drop-shadow/drop-shadow-item-content.tsx +1 -0
- package/src/controls/filter-control/drop-shadow/drop-shadow-item-label.tsx +10 -6
- package/src/controls/filter-control/filter-content.tsx +1 -1
- package/src/controls/filter-control/filter-repeater-control.tsx +24 -21
- package/src/controls/filter-control/single-size/single-size-item-content.tsx +1 -1
- package/src/controls/filter-control/single-size/single-size-item-label.tsx +2 -1
- package/src/controls/font-family-control/font-family-control.tsx +66 -55
- package/src/controls/html-tag-control.tsx +90 -0
- package/src/controls/image-media-control.tsx +2 -2
- package/src/controls/inline-editing-control.tsx +18 -0
- package/src/controls/key-value-control.tsx +8 -2
- package/src/controls/link-control.tsx +23 -123
- package/src/controls/linked-dimensions-control.tsx +71 -33
- package/src/controls/query-control.tsx +168 -0
- package/src/controls/repeatable-control.tsx +62 -27
- package/src/controls/select-control-wrapper.tsx +57 -0
- package/src/controls/select-control.tsx +9 -5
- package/src/controls/selection-size-control.tsx +13 -2
- package/src/controls/size-control.tsx +32 -59
- package/src/controls/svg-media-control.tsx +33 -10
- package/src/controls/text-area-control.tsx +5 -1
- package/src/controls/text-control.tsx +5 -0
- package/src/controls/toggle-control.tsx +11 -2
- package/src/controls/transform-control/functions/axis-row.tsx +1 -0
- package/src/controls/transform-control/transform-icon.tsx +2 -2
- package/src/controls/transform-control/transform-label.tsx +15 -32
- package/src/controls/transform-control/transform-repeater-control.tsx +42 -36
- package/src/controls/transform-control/{transform-base-control.tsx → transform-settings-control.tsx} +2 -2
- package/src/controls/transform-control/use-transform-tabs-history.tsx +1 -1
- package/src/controls/transition-control/data.ts +16 -1
- package/src/controls/transition-control/trainsition-events.ts +2 -2
- package/src/controls/transition-control/transition-repeater-control.tsx +137 -13
- package/src/controls/transition-control/transition-selector.tsx +37 -14
- package/src/controls/url-control.tsx +21 -16
- package/src/hooks/use-filtered-items-list.ts +3 -2
- package/src/hooks/use-repeatable-control-context.ts +3 -0
- package/src/hooks/use-sync-external-state.tsx +0 -1
- package/src/index.ts +21 -5
- package/src/utils/convert-toggle-options-to-atomic.tsx +33 -0
- package/src/utils/escape-html-attr.ts +11 -0
- package/src/components/css-code-editor/css-editor.styles.ts +0 -52
- package/src/components/css-code-editor/css-editor.tsx +0 -142
- package/src/components/css-code-editor/css-validation.ts +0 -75
- package/src/components/css-code-editor/resize-handle.tsx +0 -55
- package/src/components/css-code-editor/visual-content-change-protection.ts +0 -69
- package/src/components/repeater.tsx +0 -343
- package/src/components/unstable-repeater/items/item.tsx +0 -77
- package/src/components/unstable-repeater/unstable-repeater.tsx +0 -26
- /package/src/components/{unstable-repeater → control-repeater}/actions/tooltip-add-item-action.tsx +0 -0
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import type { editor, MonacoEditor } from 'monaco-types';
|
|
3
|
-
import { useActiveBreakpoint } from '@elementor/editor-responsive';
|
|
4
|
-
import { useTheme } from '@elementor/ui';
|
|
5
|
-
import { Editor } from '@monaco-editor/react';
|
|
6
|
-
|
|
7
|
-
import { EditorWrapper } from './css-editor.styles';
|
|
8
|
-
import { clearMarkersFromVisualContent, setCustomSyntaxRules, validate } from './css-validation';
|
|
9
|
-
import { ResizeHandleComponent } from './resize-handle';
|
|
10
|
-
import { preventChangeOnVisualContent } from './visual-content-change-protection';
|
|
11
|
-
|
|
12
|
-
type CssEditorProps = {
|
|
13
|
-
value: string;
|
|
14
|
-
onChange: ( value: string, isValid: boolean ) => void;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const setVisualContent = ( value: string ): string => {
|
|
18
|
-
const trimmed = value.trim();
|
|
19
|
-
return `element.style {\n${ trimmed ? ' ' + trimmed.replace( /\n/g, '\n ' ) + '\n' : ' \n' }}`;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const getActual = ( value: string ): string => {
|
|
23
|
-
const lines = value.split( '\n' );
|
|
24
|
-
if ( lines.length < 2 ) {
|
|
25
|
-
return '';
|
|
26
|
-
}
|
|
27
|
-
return lines
|
|
28
|
-
.slice( 1, -1 )
|
|
29
|
-
.map( ( line ) => line.replace( /^ {2}/, '' ) )
|
|
30
|
-
.join( '\n' );
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const createEditorDidMountHandler = (
|
|
34
|
-
editorRef: React.MutableRefObject< editor.IStandaloneCodeEditor | null >,
|
|
35
|
-
monacoRef: React.MutableRefObject< MonacoEditor | null >,
|
|
36
|
-
debounceTimer: React.MutableRefObject< NodeJS.Timeout | null >,
|
|
37
|
-
onChange: ( value: string, isValid: boolean ) => void
|
|
38
|
-
) => {
|
|
39
|
-
return ( editor: editor.IStandaloneCodeEditor, monaco: MonacoEditor ) => {
|
|
40
|
-
editorRef.current = editor;
|
|
41
|
-
monacoRef.current = monaco;
|
|
42
|
-
|
|
43
|
-
preventChangeOnVisualContent( editor );
|
|
44
|
-
|
|
45
|
-
setCustomSyntaxRules( editor, monaco );
|
|
46
|
-
|
|
47
|
-
monaco.editor.onDidChangeMarkers( () => {
|
|
48
|
-
setTimeout( () => clearMarkersFromVisualContent( editor, monaco ), 0 );
|
|
49
|
-
} );
|
|
50
|
-
|
|
51
|
-
editor.setPosition( { lineNumber: 2, column: ( editor.getModel()?.getLineContent( 2 ).length ?? 0 ) + 1 } );
|
|
52
|
-
|
|
53
|
-
editor.onDidChangeModelContent( () => {
|
|
54
|
-
const code = editor.getModel()?.getValue() ?? '';
|
|
55
|
-
const userContent = getActual( code );
|
|
56
|
-
|
|
57
|
-
setCustomSyntaxRules( editor, monaco );
|
|
58
|
-
|
|
59
|
-
const currentTimer = debounceTimer.current;
|
|
60
|
-
if ( currentTimer ) {
|
|
61
|
-
clearTimeout( currentTimer );
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const newTimer = setTimeout( () => {
|
|
65
|
-
if ( ! editorRef.current || ! monacoRef.current ) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const hasNoErrors = validate( editorRef.current, monacoRef.current );
|
|
70
|
-
|
|
71
|
-
onChange( userContent, hasNoErrors );
|
|
72
|
-
}, 500 );
|
|
73
|
-
|
|
74
|
-
debounceTimer.current = newTimer;
|
|
75
|
-
} );
|
|
76
|
-
};
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export const CssEditor = ( { value, onChange }: CssEditorProps ) => {
|
|
80
|
-
const theme = useTheme();
|
|
81
|
-
const containerRef = React.useRef< HTMLDivElement >( null );
|
|
82
|
-
const editorRef = React.useRef< editor.IStandaloneCodeEditor | null >( null );
|
|
83
|
-
const monacoRef = React.useRef< MonacoEditor | null >( null );
|
|
84
|
-
const debounceTimer = React.useRef< NodeJS.Timeout | null >( null );
|
|
85
|
-
const activeBreakpoint = useActiveBreakpoint();
|
|
86
|
-
|
|
87
|
-
const handleResize = React.useCallback( () => {
|
|
88
|
-
editorRef.current?.layout();
|
|
89
|
-
}, [] );
|
|
90
|
-
|
|
91
|
-
const handleHeightChange = React.useCallback( ( height: number ) => {
|
|
92
|
-
if ( containerRef.current ) {
|
|
93
|
-
containerRef.current.style.height = `${ height }px`;
|
|
94
|
-
}
|
|
95
|
-
}, [] );
|
|
96
|
-
|
|
97
|
-
const handleEditorDidMount = createEditorDidMountHandler( editorRef, monacoRef, debounceTimer, onChange );
|
|
98
|
-
|
|
99
|
-
React.useEffect( () => {
|
|
100
|
-
const timerRef = debounceTimer;
|
|
101
|
-
return () => {
|
|
102
|
-
const timer = timerRef.current;
|
|
103
|
-
if ( timer ) {
|
|
104
|
-
clearTimeout( timer );
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
}, [] );
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<EditorWrapper ref={ containerRef }>
|
|
111
|
-
<Editor
|
|
112
|
-
key={ activeBreakpoint }
|
|
113
|
-
height="100%"
|
|
114
|
-
language="css"
|
|
115
|
-
theme={ theme.palette.mode === 'dark' ? 'vs-dark' : 'vs' }
|
|
116
|
-
value={ setVisualContent( value ) }
|
|
117
|
-
onMount={ handleEditorDidMount }
|
|
118
|
-
options={ {
|
|
119
|
-
lineNumbers: 'on',
|
|
120
|
-
folding: true,
|
|
121
|
-
minimap: { enabled: false },
|
|
122
|
-
fontFamily: 'Roboto, Arial, Helvetica, Verdana, sans-serif',
|
|
123
|
-
fontSize: 12,
|
|
124
|
-
renderLineHighlight: 'none',
|
|
125
|
-
hideCursorInOverviewRuler: true,
|
|
126
|
-
fixedOverflowWidgets: true,
|
|
127
|
-
suggestFontSize: 10,
|
|
128
|
-
suggestLineHeight: 14,
|
|
129
|
-
stickyScroll: {
|
|
130
|
-
enabled: false,
|
|
131
|
-
},
|
|
132
|
-
lineDecorationsWidth: 2,
|
|
133
|
-
} }
|
|
134
|
-
/>
|
|
135
|
-
<ResizeHandleComponent
|
|
136
|
-
onResize={ handleResize }
|
|
137
|
-
containerRef={ containerRef }
|
|
138
|
-
onHeightChange={ handleHeightChange }
|
|
139
|
-
/>
|
|
140
|
-
</EditorWrapper>
|
|
141
|
-
);
|
|
142
|
-
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { editor, MonacoEditor } from 'monaco-types';
|
|
2
|
-
import { __ } from '@wordpress/i18n';
|
|
3
|
-
|
|
4
|
-
const forbiddenPatterns = [
|
|
5
|
-
{
|
|
6
|
-
pattern: ':hover',
|
|
7
|
-
message: __(
|
|
8
|
-
'The use of pseudo-states is not permitted. Instead, switch to the desired pseudo state and add your custom code there.',
|
|
9
|
-
'elementor'
|
|
10
|
-
),
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
pattern: ':active',
|
|
14
|
-
message: __(
|
|
15
|
-
'The use of pseudo-states is not permitted. Instead, switch to the desired pseudo state and add your custom code there.',
|
|
16
|
-
'elementor'
|
|
17
|
-
),
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
pattern: '@media',
|
|
21
|
-
message: __(
|
|
22
|
-
'The use of @media is not permitted. Instead, switch to the desired breakpoint and add your custom code there.',
|
|
23
|
-
'elementor'
|
|
24
|
-
),
|
|
25
|
-
},
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
export function setCustomSyntaxRules( editor: editor.IStandaloneCodeEditor, monaco: MonacoEditor ): boolean {
|
|
29
|
-
const model = editor.getModel();
|
|
30
|
-
if ( ! model ) {
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const customMarkers: editor.IMarkerData[] = [];
|
|
35
|
-
|
|
36
|
-
forbiddenPatterns.forEach( ( rule ) => {
|
|
37
|
-
const matches = model.findMatches( rule.pattern, true, false, true, null, true );
|
|
38
|
-
matches.forEach( ( match ) => {
|
|
39
|
-
customMarkers.push( {
|
|
40
|
-
severity: monaco.MarkerSeverity.Error,
|
|
41
|
-
message: rule.message,
|
|
42
|
-
startLineNumber: match.range.startLineNumber,
|
|
43
|
-
startColumn: match.range.startColumn,
|
|
44
|
-
endLineNumber: match.range.endLineNumber,
|
|
45
|
-
endColumn: match.range.endColumn,
|
|
46
|
-
source: 'custom-css-rules',
|
|
47
|
-
} );
|
|
48
|
-
} );
|
|
49
|
-
} );
|
|
50
|
-
|
|
51
|
-
monaco.editor.setModelMarkers( model, 'custom-css-rules', customMarkers );
|
|
52
|
-
return customMarkers.length === 0;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function validate( editor: editor.IStandaloneCodeEditor, monaco: MonacoEditor ): boolean {
|
|
56
|
-
const model = editor.getModel();
|
|
57
|
-
if ( ! model ) {
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
const allMarkers = monaco.editor.getModelMarkers( { resource: model.uri } );
|
|
61
|
-
return allMarkers.filter( ( marker ) => marker.severity === monaco.MarkerSeverity.Error ).length === 0;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function clearMarkersFromVisualContent( editor: editor.IStandaloneCodeEditor, monaco: MonacoEditor ): void {
|
|
65
|
-
const model = editor.getModel();
|
|
66
|
-
|
|
67
|
-
if ( ! model ) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const allMarkers = monaco.editor.getModelMarkers( { resource: model.uri } );
|
|
72
|
-
const filteredMarkers = allMarkers.filter( ( marker ) => marker.startLineNumber !== 1 );
|
|
73
|
-
const nonCustomMarkers = filteredMarkers.filter( ( m ) => m.source !== 'custom-css-rules' );
|
|
74
|
-
monaco.editor.setModelMarkers( model, 'css', nonCustomMarkers );
|
|
75
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
import { ResizeHandle } from './css-editor.styles';
|
|
4
|
-
|
|
5
|
-
type ResizeHandleProps = {
|
|
6
|
-
onResize: ( height: number ) => void;
|
|
7
|
-
containerRef: React.RefObject< HTMLDivElement >;
|
|
8
|
-
onHeightChange?: ( height: number ) => void;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const ResizeHandleComponent = ( { onResize, containerRef, onHeightChange }: ResizeHandleProps ) => {
|
|
12
|
-
const handleResizeMove = React.useCallback(
|
|
13
|
-
( e: MouseEvent ) => {
|
|
14
|
-
const container = containerRef.current;
|
|
15
|
-
if ( ! container ) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
const containerRect = container.getBoundingClientRect();
|
|
19
|
-
const newHeight = Math.max( 100, e.clientY - containerRect.top );
|
|
20
|
-
onHeightChange?.( newHeight );
|
|
21
|
-
onResize( newHeight );
|
|
22
|
-
},
|
|
23
|
-
[ containerRef, onResize, onHeightChange ]
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
const handleResizeEnd = React.useCallback( () => {
|
|
27
|
-
document.removeEventListener( 'mousemove', handleResizeMove );
|
|
28
|
-
document.removeEventListener( 'mouseup', handleResizeEnd );
|
|
29
|
-
}, [ handleResizeMove ] );
|
|
30
|
-
|
|
31
|
-
const handleResizeStart = React.useCallback(
|
|
32
|
-
( e: React.MouseEvent ) => {
|
|
33
|
-
e.preventDefault();
|
|
34
|
-
e.stopPropagation();
|
|
35
|
-
document.addEventListener( 'mousemove', handleResizeMove );
|
|
36
|
-
document.addEventListener( 'mouseup', handleResizeEnd );
|
|
37
|
-
},
|
|
38
|
-
[ handleResizeMove, handleResizeEnd ]
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
React.useEffect( () => {
|
|
42
|
-
return () => {
|
|
43
|
-
document.removeEventListener( 'mousemove', handleResizeMove );
|
|
44
|
-
document.removeEventListener( 'mouseup', handleResizeEnd );
|
|
45
|
-
};
|
|
46
|
-
}, [ handleResizeMove, handleResizeEnd ] );
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<ResizeHandle
|
|
50
|
-
onMouseDown={ handleResizeStart }
|
|
51
|
-
aria-label="Resize editor height"
|
|
52
|
-
title="Drag to resize editor height"
|
|
53
|
-
/>
|
|
54
|
-
);
|
|
55
|
-
};
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { editor } from 'monaco-types';
|
|
2
|
-
|
|
3
|
-
export const preventChangeOnVisualContent = ( editor: editor.IStandaloneCodeEditor ) => {
|
|
4
|
-
const model = editor.getModel();
|
|
5
|
-
if ( ! model ) {
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const decorationsCollection = editor.createDecorationsCollection();
|
|
10
|
-
|
|
11
|
-
const applyVisualContentStyling = () => {
|
|
12
|
-
const totalLines = model.getLineCount();
|
|
13
|
-
const decorations = [];
|
|
14
|
-
|
|
15
|
-
decorations.push( {
|
|
16
|
-
range: {
|
|
17
|
-
startLineNumber: 1,
|
|
18
|
-
startColumn: 1,
|
|
19
|
-
endLineNumber: 1,
|
|
20
|
-
endColumn: model.getLineContent( 1 ).length + 1,
|
|
21
|
-
},
|
|
22
|
-
options: {
|
|
23
|
-
inlineClassName: 'visual-content-dimmed',
|
|
24
|
-
isWholeLine: false,
|
|
25
|
-
},
|
|
26
|
-
} );
|
|
27
|
-
|
|
28
|
-
if ( totalLines > 1 ) {
|
|
29
|
-
decorations.push( {
|
|
30
|
-
range: {
|
|
31
|
-
startLineNumber: totalLines,
|
|
32
|
-
startColumn: 1,
|
|
33
|
-
endLineNumber: totalLines,
|
|
34
|
-
endColumn: model.getLineContent( totalLines ).length + 1,
|
|
35
|
-
},
|
|
36
|
-
options: {
|
|
37
|
-
inlineClassName: 'visual-content-dimmed',
|
|
38
|
-
isWholeLine: false,
|
|
39
|
-
},
|
|
40
|
-
} );
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
decorationsCollection.set( decorations );
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
applyVisualContentStyling();
|
|
47
|
-
|
|
48
|
-
model.onDidChangeContent( () => {
|
|
49
|
-
applyVisualContentStyling();
|
|
50
|
-
} );
|
|
51
|
-
|
|
52
|
-
const originalPushEditOperations = model.pushEditOperations;
|
|
53
|
-
model.pushEditOperations = function ( beforeCursorState, editOperations, cursorStateComputer ) {
|
|
54
|
-
const totalLines = model.getLineCount();
|
|
55
|
-
|
|
56
|
-
const filteredOperations = editOperations.filter( ( operation ) => {
|
|
57
|
-
const range = operation.range;
|
|
58
|
-
const affectsProtectedLine =
|
|
59
|
-
range.startLineNumber === 1 ||
|
|
60
|
-
range.endLineNumber === 1 ||
|
|
61
|
-
range.startLineNumber === totalLines ||
|
|
62
|
-
range.endLineNumber === totalLines;
|
|
63
|
-
|
|
64
|
-
return ! affectsProtectedLine;
|
|
65
|
-
} );
|
|
66
|
-
|
|
67
|
-
return originalPushEditOperations.call( this, beforeCursorState, filteredOperations, cursorStateComputer );
|
|
68
|
-
};
|
|
69
|
-
};
|
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import { type PropKey, type PropTypeUtil } from '@elementor/editor-props';
|
|
4
|
-
import { CopyIcon, EyeIcon, EyeOffIcon, PlusIcon, XIcon } from '@elementor/icons';
|
|
5
|
-
import {
|
|
6
|
-
bindPopover,
|
|
7
|
-
bindTrigger,
|
|
8
|
-
Box,
|
|
9
|
-
IconButton,
|
|
10
|
-
Popover,
|
|
11
|
-
Stack,
|
|
12
|
-
Tooltip,
|
|
13
|
-
Typography,
|
|
14
|
-
UnstableTag,
|
|
15
|
-
type UnstableTagProps,
|
|
16
|
-
usePopupState,
|
|
17
|
-
} from '@elementor/ui';
|
|
18
|
-
import { __ } from '@wordpress/i18n';
|
|
19
|
-
|
|
20
|
-
import { ControlAdornments } from '../control-adornments/control-adornments';
|
|
21
|
-
import { useSyncExternalState } from '../hooks/use-sync-external-state';
|
|
22
|
-
import { SectionContent } from './section-content';
|
|
23
|
-
import { SortableItem, SortableProvider } from './sortable';
|
|
24
|
-
import { RepeaterItemIconSlot, RepeaterItemLabelSlot } from './unstable-repeater/locations';
|
|
25
|
-
|
|
26
|
-
const SIZE = 'tiny';
|
|
27
|
-
|
|
28
|
-
type AnchorEl = HTMLElement | null;
|
|
29
|
-
|
|
30
|
-
type Item< T > = {
|
|
31
|
-
disabled?: boolean;
|
|
32
|
-
} & T;
|
|
33
|
-
export type CollectionPropUtil< T > = PropTypeUtil< PropKey, T[] >;
|
|
34
|
-
|
|
35
|
-
type RepeaterItemContentProps< T > = {
|
|
36
|
-
anchorEl: AnchorEl;
|
|
37
|
-
bind: PropKey;
|
|
38
|
-
value: T;
|
|
39
|
-
collectionPropUtil?: CollectionPropUtil< T >;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
type RepeaterItemContent< T > = React.ComponentType< RepeaterItemContentProps< T > >;
|
|
43
|
-
|
|
44
|
-
type RepeaterProps< T > = {
|
|
45
|
-
label: string;
|
|
46
|
-
values?: T[];
|
|
47
|
-
addToBottom?: boolean;
|
|
48
|
-
openOnAdd?: boolean;
|
|
49
|
-
setValues: ( newValue: T[] ) => void;
|
|
50
|
-
disabled?: boolean;
|
|
51
|
-
itemSettings: {
|
|
52
|
-
initialValues: T;
|
|
53
|
-
Label: React.ComponentType< { value: T } >;
|
|
54
|
-
Icon: React.ComponentType< { value: T } >;
|
|
55
|
-
Content: RepeaterItemContent< T >;
|
|
56
|
-
};
|
|
57
|
-
showDuplicate?: boolean;
|
|
58
|
-
showToggle?: boolean;
|
|
59
|
-
isSortable?: boolean;
|
|
60
|
-
collectionPropUtil?: CollectionPropUtil< T >;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const EMPTY_OPEN_ITEM = -1;
|
|
64
|
-
|
|
65
|
-
export const Repeater = < T, >( {
|
|
66
|
-
label,
|
|
67
|
-
itemSettings,
|
|
68
|
-
disabled = false,
|
|
69
|
-
openOnAdd = false,
|
|
70
|
-
addToBottom = false,
|
|
71
|
-
values: repeaterValues = [],
|
|
72
|
-
setValues: setRepeaterValues,
|
|
73
|
-
showDuplicate = true,
|
|
74
|
-
showToggle = true,
|
|
75
|
-
isSortable = true,
|
|
76
|
-
collectionPropUtil,
|
|
77
|
-
}: RepeaterProps< Item< T > > ) => {
|
|
78
|
-
const [ openItem, setOpenItem ] = useState( EMPTY_OPEN_ITEM );
|
|
79
|
-
|
|
80
|
-
const [ items, setItems ] = useSyncExternalState( {
|
|
81
|
-
external: repeaterValues,
|
|
82
|
-
// @ts-expect-error - as long as persistWhen => true, value will never be null
|
|
83
|
-
setExternal: setRepeaterValues,
|
|
84
|
-
persistWhen: () => true,
|
|
85
|
-
} );
|
|
86
|
-
|
|
87
|
-
const [ uniqueKeys, setUniqueKeys ] = useState( items.map( ( _, index ) => index ) );
|
|
88
|
-
|
|
89
|
-
const generateNextKey = ( source: number[] ) => {
|
|
90
|
-
return 1 + Math.max( 0, ...source );
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const addRepeaterItem = () => {
|
|
94
|
-
const newItem = structuredClone( itemSettings.initialValues );
|
|
95
|
-
const newKey = generateNextKey( uniqueKeys );
|
|
96
|
-
|
|
97
|
-
if ( addToBottom ) {
|
|
98
|
-
setItems( [ ...items, newItem ] );
|
|
99
|
-
setUniqueKeys( [ ...uniqueKeys, newKey ] );
|
|
100
|
-
} else {
|
|
101
|
-
setItems( [ newItem, ...items ] );
|
|
102
|
-
setUniqueKeys( [ newKey, ...uniqueKeys ] );
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if ( openOnAdd ) {
|
|
106
|
-
setOpenItem( newKey );
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const duplicateRepeaterItem = ( index: number ) => {
|
|
111
|
-
const newItem = structuredClone( items[ index ] );
|
|
112
|
-
const newKey = generateNextKey( uniqueKeys );
|
|
113
|
-
|
|
114
|
-
// Insert the new (cloned item) at the next spot (after the current index)
|
|
115
|
-
const atPosition = 1 + index;
|
|
116
|
-
|
|
117
|
-
setItems( [ ...items.slice( 0, atPosition ), newItem, ...items.slice( atPosition ) ] );
|
|
118
|
-
setUniqueKeys( [ ...uniqueKeys.slice( 0, atPosition ), newKey, ...uniqueKeys.slice( atPosition ) ] );
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const removeRepeaterItem = ( index: number ) => {
|
|
122
|
-
setUniqueKeys(
|
|
123
|
-
uniqueKeys.filter( ( _, pos ) => {
|
|
124
|
-
return pos !== index;
|
|
125
|
-
} )
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
setItems(
|
|
129
|
-
items.filter( ( _, pos ) => {
|
|
130
|
-
return pos !== index;
|
|
131
|
-
} )
|
|
132
|
-
);
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const toggleDisableRepeaterItem = ( index: number ) => {
|
|
136
|
-
setItems(
|
|
137
|
-
items.map( ( value, pos ) => {
|
|
138
|
-
if ( pos === index ) {
|
|
139
|
-
const { disabled: propDisabled, ...rest } = value;
|
|
140
|
-
|
|
141
|
-
// If the items should not be disabled, remove the disabled property.
|
|
142
|
-
return { ...rest, ...( propDisabled ? {} : { disabled: true } ) } as Item< T >;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return value;
|
|
146
|
-
} )
|
|
147
|
-
);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const onChangeOrder = ( reorderedKeys: number[] ) => {
|
|
151
|
-
setUniqueKeys( reorderedKeys );
|
|
152
|
-
setItems( ( prevItems ) => {
|
|
153
|
-
return reorderedKeys.map( ( keyValue ) => {
|
|
154
|
-
const index = uniqueKeys.indexOf( keyValue );
|
|
155
|
-
return prevItems[ index ];
|
|
156
|
-
} );
|
|
157
|
-
} );
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<SectionContent>
|
|
162
|
-
<Stack
|
|
163
|
-
direction="row"
|
|
164
|
-
justifyContent="start"
|
|
165
|
-
alignItems="center"
|
|
166
|
-
gap={ 1 }
|
|
167
|
-
sx={ { marginInlineEnd: -0.75 } }
|
|
168
|
-
>
|
|
169
|
-
<Typography component="label" variant="caption" color="text.secondary">
|
|
170
|
-
{ label }
|
|
171
|
-
</Typography>
|
|
172
|
-
<ControlAdornments />
|
|
173
|
-
<IconButton
|
|
174
|
-
size={ SIZE }
|
|
175
|
-
sx={ { ml: 'auto' } }
|
|
176
|
-
disabled={ disabled }
|
|
177
|
-
onClick={ addRepeaterItem }
|
|
178
|
-
aria-label={ __( 'Add item', 'elementor' ) }
|
|
179
|
-
>
|
|
180
|
-
<PlusIcon fontSize={ SIZE } />
|
|
181
|
-
</IconButton>
|
|
182
|
-
</Stack>
|
|
183
|
-
{ 0 < uniqueKeys.length && (
|
|
184
|
-
<SortableProvider value={ uniqueKeys } onChange={ onChangeOrder }>
|
|
185
|
-
{ uniqueKeys.map( ( key, index ) => {
|
|
186
|
-
const value = items[ index ];
|
|
187
|
-
|
|
188
|
-
if ( ! value ) {
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return (
|
|
193
|
-
<SortableItem id={ key } key={ `sortable-${ key }` } disabled={ ! isSortable }>
|
|
194
|
-
<RepeaterItem
|
|
195
|
-
disabled={ disabled }
|
|
196
|
-
propDisabled={ value?.disabled }
|
|
197
|
-
label={
|
|
198
|
-
<RepeaterItemLabelSlot value={ value }>
|
|
199
|
-
<itemSettings.Label value={ value } />
|
|
200
|
-
</RepeaterItemLabelSlot>
|
|
201
|
-
}
|
|
202
|
-
startIcon={
|
|
203
|
-
<RepeaterItemIconSlot value={ value }>
|
|
204
|
-
<itemSettings.Icon value={ value } />
|
|
205
|
-
</RepeaterItemIconSlot>
|
|
206
|
-
}
|
|
207
|
-
removeItem={ () => removeRepeaterItem( index ) }
|
|
208
|
-
duplicateItem={ () => duplicateRepeaterItem( index ) }
|
|
209
|
-
toggleDisableItem={ () => toggleDisableRepeaterItem( index ) }
|
|
210
|
-
openOnMount={ openOnAdd && openItem === key }
|
|
211
|
-
onOpen={ () => setOpenItem( EMPTY_OPEN_ITEM ) }
|
|
212
|
-
showDuplicate={ showDuplicate }
|
|
213
|
-
showToggle={ showToggle }
|
|
214
|
-
collectionPropUtil={ collectionPropUtil }
|
|
215
|
-
>
|
|
216
|
-
{ ( props ) => (
|
|
217
|
-
<itemSettings.Content { ...props } value={ value } bind={ String( index ) } />
|
|
218
|
-
) }
|
|
219
|
-
</RepeaterItem>
|
|
220
|
-
</SortableItem>
|
|
221
|
-
);
|
|
222
|
-
} ) }
|
|
223
|
-
</SortableProvider>
|
|
224
|
-
) }
|
|
225
|
-
</SectionContent>
|
|
226
|
-
);
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
type RepeaterItemProps< T > = {
|
|
230
|
-
label: React.ReactNode;
|
|
231
|
-
propDisabled?: boolean;
|
|
232
|
-
startIcon: UnstableTagProps[ 'startIcon' ];
|
|
233
|
-
removeItem: () => void;
|
|
234
|
-
duplicateItem: () => void;
|
|
235
|
-
toggleDisableItem: () => void;
|
|
236
|
-
children: ( props: Pick< RepeaterItemContentProps< T >, 'anchorEl' | 'collectionPropUtil' > ) => React.ReactNode;
|
|
237
|
-
openOnMount: boolean;
|
|
238
|
-
onOpen: () => void;
|
|
239
|
-
showDuplicate: boolean;
|
|
240
|
-
showToggle: boolean;
|
|
241
|
-
disabled?: boolean;
|
|
242
|
-
collectionPropUtil?: CollectionPropUtil< T >;
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const RepeaterItem = < T, >( {
|
|
246
|
-
label,
|
|
247
|
-
propDisabled,
|
|
248
|
-
startIcon,
|
|
249
|
-
children,
|
|
250
|
-
removeItem,
|
|
251
|
-
duplicateItem,
|
|
252
|
-
toggleDisableItem,
|
|
253
|
-
openOnMount,
|
|
254
|
-
onOpen,
|
|
255
|
-
showDuplicate,
|
|
256
|
-
showToggle,
|
|
257
|
-
disabled,
|
|
258
|
-
collectionPropUtil,
|
|
259
|
-
}: RepeaterItemProps< T > ) => {
|
|
260
|
-
const [ anchorEl, setAnchorEl ] = useState< AnchorEl >( null );
|
|
261
|
-
const { popoverState, popoverProps, ref, setRef } = usePopover( openOnMount, onOpen );
|
|
262
|
-
|
|
263
|
-
const duplicateLabel = __( 'Duplicate', 'elementor' );
|
|
264
|
-
const toggleLabel = propDisabled ? __( 'Show', 'elementor' ) : __( 'Hide', 'elementor' );
|
|
265
|
-
const removeLabel = __( 'Remove', 'elementor' );
|
|
266
|
-
|
|
267
|
-
return (
|
|
268
|
-
<>
|
|
269
|
-
<UnstableTag
|
|
270
|
-
disabled={ disabled }
|
|
271
|
-
label={ label }
|
|
272
|
-
showActionsOnHover
|
|
273
|
-
fullWidth
|
|
274
|
-
ref={ setRef }
|
|
275
|
-
variant="outlined"
|
|
276
|
-
aria-label={ __( 'Open item', 'elementor' ) }
|
|
277
|
-
{ ...bindTrigger( popoverState ) }
|
|
278
|
-
startIcon={ startIcon }
|
|
279
|
-
actions={
|
|
280
|
-
<>
|
|
281
|
-
{ showDuplicate && (
|
|
282
|
-
<Tooltip title={ duplicateLabel } placement="top">
|
|
283
|
-
<IconButton size={ SIZE } onClick={ duplicateItem } aria-label={ duplicateLabel }>
|
|
284
|
-
<CopyIcon fontSize={ SIZE } />
|
|
285
|
-
</IconButton>
|
|
286
|
-
</Tooltip>
|
|
287
|
-
) }
|
|
288
|
-
{ showToggle && (
|
|
289
|
-
<Tooltip title={ toggleLabel } placement="top">
|
|
290
|
-
<IconButton size={ SIZE } onClick={ toggleDisableItem } aria-label={ toggleLabel }>
|
|
291
|
-
{ propDisabled ? <EyeOffIcon fontSize={ SIZE } /> : <EyeIcon fontSize={ SIZE } /> }
|
|
292
|
-
</IconButton>
|
|
293
|
-
</Tooltip>
|
|
294
|
-
) }
|
|
295
|
-
<Tooltip title={ removeLabel } placement="top">
|
|
296
|
-
<IconButton size={ SIZE } onClick={ removeItem } aria-label={ removeLabel }>
|
|
297
|
-
<XIcon fontSize={ SIZE } />
|
|
298
|
-
</IconButton>
|
|
299
|
-
</Tooltip>
|
|
300
|
-
</>
|
|
301
|
-
}
|
|
302
|
-
/>
|
|
303
|
-
<Popover
|
|
304
|
-
disablePortal
|
|
305
|
-
slotProps={ {
|
|
306
|
-
paper: {
|
|
307
|
-
ref: setAnchorEl,
|
|
308
|
-
sx: { mt: 0.5, width: ref?.getBoundingClientRect().width },
|
|
309
|
-
},
|
|
310
|
-
} }
|
|
311
|
-
anchorOrigin={ { vertical: 'bottom', horizontal: 'left' } }
|
|
312
|
-
{ ...popoverProps }
|
|
313
|
-
anchorEl={ ref }
|
|
314
|
-
>
|
|
315
|
-
<Box>{ children( { anchorEl, collectionPropUtil } ) }</Box>
|
|
316
|
-
</Popover>
|
|
317
|
-
</>
|
|
318
|
-
);
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
const usePopover = ( openOnMount: boolean, onOpen: () => void ) => {
|
|
322
|
-
const [ ref, setRef ] = useState< HTMLElement | null >( null );
|
|
323
|
-
|
|
324
|
-
const popoverState = usePopupState( { variant: 'popover' } );
|
|
325
|
-
|
|
326
|
-
const popoverProps = bindPopover( popoverState );
|
|
327
|
-
|
|
328
|
-
useEffect( () => {
|
|
329
|
-
if ( openOnMount && ref ) {
|
|
330
|
-
popoverState.open( ref );
|
|
331
|
-
onOpen?.();
|
|
332
|
-
}
|
|
333
|
-
// eslint-disable-next-line react-compiler/react-compiler
|
|
334
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
335
|
-
}, [ ref ] );
|
|
336
|
-
|
|
337
|
-
return {
|
|
338
|
-
popoverState,
|
|
339
|
-
ref,
|
|
340
|
-
setRef,
|
|
341
|
-
popoverProps,
|
|
342
|
-
};
|
|
343
|
-
};
|