@elementor/editor-controls 1.0.0 → 1.2.0

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 (33) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/index.d.mts +78 -41
  3. package/dist/index.d.ts +78 -41
  4. package/dist/index.js +875 -617
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +713 -467
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +11 -11
  9. package/src/components/font-family-selector.tsx +50 -174
  10. package/src/components/popover-content.tsx +3 -11
  11. package/src/components/repeater.tsx +27 -11
  12. package/src/components/text-field-popover.tsx +3 -3
  13. package/src/controls/aspect-ratio-control.tsx +20 -2
  14. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-position.tsx +2 -2
  15. package/src/controls/background-control/background-overlay/background-image-overlay/background-image-overlay-size.tsx +2 -2
  16. package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +9 -4
  17. package/src/controls/box-shadow-repeater-control.tsx +2 -2
  18. package/src/controls/equal-unequal-sizes-control.tsx +3 -9
  19. package/src/controls/filter-repeater-control.tsx +186 -0
  20. package/src/controls/font-family-control/font-family-control.tsx +6 -2
  21. package/src/controls/gap-control.tsx +3 -3
  22. package/src/controls/image-control.tsx +22 -35
  23. package/src/controls/key-value-control.tsx +119 -0
  24. package/src/controls/link-control.tsx +3 -1
  25. package/src/controls/linked-dimensions-control.tsx +3 -3
  26. package/src/controls/number-control.tsx +3 -3
  27. package/src/controls/position-control.tsx +109 -0
  28. package/src/controls/repeatable-control.tsx +119 -0
  29. package/src/controls/size-control.tsx +11 -9
  30. package/src/controls/stroke-control.tsx +2 -2
  31. package/src/controls/svg-media-control.tsx +0 -2
  32. package/src/hooks/use-repeatable-control-context.ts +24 -0
  33. package/src/index.ts +6 -1
@@ -0,0 +1,119 @@
1
+ import * as React from 'react';
2
+ import { useMemo } from 'react';
3
+ import { createArrayPropUtils, type PropKey } from '@elementor/editor-props';
4
+ import { Box } from '@elementor/ui';
5
+
6
+ /* eslint-disable */
7
+ import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
8
+ import { PopoverContent } from '../components/popover-content';
9
+ import { PopoverGridContainer } from '../components/popover-grid-container';
10
+ import { Repeater } from '../components/repeater';
11
+ import { createControl } from '../create-control';
12
+ import {
13
+ type ChildControlConfig,
14
+ RepeatableControlContext,
15
+ useRepeatableControlContext,
16
+ } from '../hooks/use-repeatable-control-context';
17
+
18
+ type RepeatableControlProps = {
19
+ label: string;
20
+ repeaterLabel: string;
21
+ childControlConfig: ChildControlConfig;
22
+ showDuplicate?: boolean;
23
+ showToggle?: boolean;
24
+ initialValues?: object;
25
+ patternLabel?: string;
26
+ placeholder?: string;
27
+ };
28
+
29
+ export const RepeatableControl = createControl(
30
+ ( {
31
+ repeaterLabel,
32
+ childControlConfig,
33
+ showDuplicate,
34
+ showToggle,
35
+ initialValues,
36
+ patternLabel,
37
+ placeholder,
38
+ }: RepeatableControlProps ) => {
39
+ const { propTypeUtil: childPropTypeUtil } = childControlConfig;
40
+
41
+ if ( ! childPropTypeUtil ) {
42
+ return null;
43
+ }
44
+
45
+ const childArrayPropTypeUtil = useMemo(
46
+ () => createArrayPropUtils( childPropTypeUtil.key, childPropTypeUtil.schema ),
47
+ [ childPropTypeUtil.key, childPropTypeUtil.schema ]
48
+ );
49
+
50
+ const { propType, value, setValue } = useBoundProp( childArrayPropTypeUtil );
51
+ const ItemLabel = ( { value: itemValue }: { value: any } ) => {
52
+ const pattern = patternLabel || '';
53
+ const finalLabel = interpolate( pattern, itemValue.value );
54
+
55
+ if ( finalLabel ) {
56
+ return <span>{ finalLabel }</span>;
57
+ }
58
+
59
+ return (
60
+ <Box component="span" color="text.tertiary">
61
+ { placeholder }
62
+ </Box>
63
+ );
64
+ };
65
+
66
+ return (
67
+ <PropProvider propType={ propType } value={ value } setValue={ setValue }>
68
+ <RepeatableControlContext.Provider value={ childControlConfig }>
69
+ <Repeater
70
+ openOnAdd
71
+ values={ value ?? [] }
72
+ setValues={ setValue }
73
+ label={ repeaterLabel }
74
+ isSortable={ false }
75
+ itemSettings={ {
76
+ Icon: ItemIcon,
77
+ Label: ItemLabel,
78
+ Content: ItemContent,
79
+ initialValues: childPropTypeUtil.create( initialValues || null ),
80
+ } }
81
+ showDuplicate={ showDuplicate }
82
+ showToggle={ showToggle }
83
+ />
84
+ </RepeatableControlContext.Provider>
85
+ </PropProvider>
86
+ );
87
+ }
88
+ );
89
+
90
+ const ItemContent = ( { bind }: { bind: PropKey } ) => {
91
+ return (
92
+ <PropKeyProvider bind={ bind }>
93
+ <Content />
94
+ </PropKeyProvider>
95
+ );
96
+ };
97
+
98
+ // TODO: Configurable icon probably can be somehow part of the injected control and bubbled up to the repeater
99
+ const ItemIcon = () => <></>;
100
+
101
+ const Content = () => {
102
+ const { component: ChildControl, props = {} } = useRepeatableControlContext();
103
+
104
+ return (
105
+ <PopoverContent p={ 1.5 }>
106
+ <PopoverGridContainer>
107
+ <ChildControl { ...props } />
108
+ </PopoverGridContainer>
109
+ </PopoverContent>
110
+ );
111
+ };
112
+
113
+ const interpolate = ( template: string, data: object ) => {
114
+ //remove it to be generic
115
+ if ( Object.values( data ).some( ( value ) => value.value === '' || value === '' ) ) {
116
+ return null;
117
+ }
118
+ return new Function( ...Object.keys( data ), `return \`${ template }\`;` )( ...Object.values( data ) );
119
+ };
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { type MutableRefObject, useEffect, useState } from 'react';
2
+ import { type RefObject, useEffect, useState } from 'react';
3
3
  import { sizePropTypeUtil, type SizePropValue } from '@elementor/editor-props';
4
4
  import { useActiveBreakpoint } from '@elementor/editor-responsive';
5
5
  import { usePopupState } from '@elementor/ui';
@@ -23,7 +23,8 @@ type SizeControlProps = {
23
23
  units?: Unit[];
24
24
  extendedOptions?: ExtendedOption[];
25
25
  disableCustom?: boolean;
26
- anchorRef?: MutableRefObject< HTMLElement | undefined >;
26
+ anchorRef?: RefObject< HTMLDivElement | null >;
27
+ defaultUnit?: Unit;
27
28
  };
28
29
 
29
30
  type State = {
@@ -33,9 +34,10 @@ type State = {
33
34
  };
34
35
 
35
36
  export const SizeControl = createControl( ( props: SizeControlProps ) => {
37
+ const defaultUnit = props.defaultUnit ?? DEFAULT_UNIT;
36
38
  const { units = [ ...defaultUnits ], placeholder, startIcon, anchorRef } = props;
37
39
  const { value: sizeValue, setValue: setSizeValue, disabled, restoreValue } = useBoundProp( sizePropTypeUtil );
38
- const [ internalState, setInternalState ] = useState( createStateFromSizeProp( sizeValue ) );
40
+ const [ internalState, setInternalState ] = useState( createStateFromSizeProp( sizeValue, defaultUnit ) );
39
41
  const activeBreakpoint = useActiveBreakpoint();
40
42
 
41
43
  const extendedOptions = useSizeExtendedOptions( props.extendedOptions || [], props.disableCustom ?? false );
@@ -56,7 +58,7 @@ export const SizeControl = createControl( ( props: SizeControlProps ) => {
56
58
  return !! newState?.numeric || newState?.numeric === 0;
57
59
  },
58
60
  fallback: ( newState ) => ( {
59
- unit: newState?.unit ?? DEFAULT_UNIT,
61
+ unit: newState?.unit ?? props.defaultUnit ?? DEFAULT_UNIT,
60
62
  numeric: newState?.numeric ?? DEFAULT_SIZE,
61
63
  custom: newState?.custom ?? '',
62
64
  } ),
@@ -101,7 +103,7 @@ export const SizeControl = createControl( ( props: SizeControlProps ) => {
101
103
  };
102
104
 
103
105
  useEffect( () => {
104
- const newState = createStateFromSizeProp( sizeValue );
106
+ const newState = createStateFromSizeProp( sizeValue, defaultUnit );
105
107
  const currentUnit = isUnitExtendedOption( state.unit ) ? 'custom' : 'numeric';
106
108
  const mergedStates = { ...state, [ currentUnit ]: newState[ currentUnit ] };
107
109
 
@@ -120,7 +122,7 @@ export const SizeControl = createControl( ( props: SizeControlProps ) => {
120
122
  }, [ sizeValue ] );
121
123
 
122
124
  useEffect( () => {
123
- const newState = createStateFromSizeProp( sizeValue );
125
+ const newState = createStateFromSizeProp( sizeValue, defaultUnit );
124
126
 
125
127
  if ( activeBreakpoint && ! areStatesEqual( newState, state ) ) {
126
128
  setState( newState );
@@ -147,7 +149,7 @@ export const SizeControl = createControl( ( props: SizeControlProps ) => {
147
149
  { anchorRef?.current && (
148
150
  <TextFieldPopover
149
151
  popupState={ popupState }
150
- anchorRef={ anchorRef as MutableRefObject< HTMLElement > }
152
+ anchorRef={ anchorRef }
151
153
  restoreValue={ restoreValue }
152
154
  value={ controlSize as string }
153
155
  onChange={ handleSizeChange }
@@ -165,8 +167,8 @@ function formatSize< TSize extends string | number >( size: TSize, unit: Unit |
165
167
  return size || size === 0 ? ( Number( size ) as TSize ) : ( NaN as TSize );
166
168
  }
167
169
 
168
- function createStateFromSizeProp( sizeValue: SizeValue | null ): State {
169
- const unit = sizeValue?.unit ?? DEFAULT_UNIT;
170
+ function createStateFromSizeProp( sizeValue: SizeValue | null, defaultUnit: Unit ): State {
171
+ const unit = sizeValue?.unit ?? defaultUnit;
170
172
  const size = sizeValue?.size ?? '';
171
173
 
172
174
  return {
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { forwardRef, type MutableRefObject, useRef } from 'react';
2
+ import { forwardRef, useRef } from 'react';
3
3
  import { strokePropTypeUtil } from '@elementor/editor-props';
4
4
  import { Grid } from '@elementor/ui';
5
5
  import { __ } from '@wordpress/i18n';
@@ -22,7 +22,7 @@ const units: Unit[] = [ 'px', 'em', 'rem' ];
22
22
 
23
23
  export const StrokeControl = createControl( () => {
24
24
  const propContext = useBoundProp( strokePropTypeUtil );
25
- const rowRef: MutableRefObject< HTMLElement | undefined > = useRef();
25
+ const rowRef = useRef< HTMLDivElement >( null );
26
26
 
27
27
  return (
28
28
  <PropProvider { ...propContext }>
@@ -7,7 +7,6 @@ import { type OpenOptions, useWpMediaAttachment, useWpMediaFrame } from '@elemen
7
7
  import { __ } from '@wordpress/i18n';
8
8
 
9
9
  import { useBoundProp } from '../bound-prop-context';
10
- import { ControlFormLabel } from '../components/control-form-label';
11
10
  import { EnableUnfilteredModal } from '../components/enable-unfiltered-modal';
12
11
  import ControlActions from '../control-actions/control-actions';
13
12
  import { createControl } from '../create-control';
@@ -83,7 +82,6 @@ export const SvgMediaControl = createControl( () => {
83
82
  return (
84
83
  <Stack gap={ 1 }>
85
84
  <EnableUnfilteredModal open={ unfilteredModalOpenState } onClose={ onCloseUnfilteredModal } />
86
- <ControlFormLabel> { __( 'SVG', 'elementor' ) } </ControlFormLabel>
87
85
  <ControlActions>
88
86
  <StyledCard variant="outlined">
89
87
  <StyledCardMediaContainer>
@@ -0,0 +1,24 @@
1
+ import { createContext, useContext } from 'react';
2
+ import { type PropTypeUtil } from '@elementor/editor-props';
3
+
4
+ export type ChildControlConfig = {
5
+ component: React.ComponentType;
6
+ props?: Record< string, unknown >;
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ propTypeUtil: PropTypeUtil< string, any >;
9
+ label?: string;
10
+ };
11
+
12
+ const RepeatableControlContext = createContext< ChildControlConfig | undefined >( undefined );
13
+
14
+ const useRepeatableControlContext = () => {
15
+ const context = useContext( RepeatableControlContext );
16
+
17
+ if ( ! context ) {
18
+ throw new Error( 'useRepeatableControlContext must be used within RepeatableControl' );
19
+ }
20
+
21
+ return context;
22
+ };
23
+
24
+ export { RepeatableControlContext, useRepeatableControlContext };
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export { TextAreaControl } from './controls/text-area-control';
5
5
  export { SizeControl } from './controls/size-control';
6
6
  export { StrokeControl } from './controls/stroke-control';
7
7
  export { BoxShadowRepeaterControl } from './controls/box-shadow-repeater-control';
8
+ export { FilterRepeaterControl } from './controls/filter-repeater-control';
8
9
  export { SelectControl } from './controls/select-control';
9
10
  export { ColorControl } from './controls/color-control';
10
11
  export { ToggleControl } from './controls/toggle-control';
@@ -19,6 +20,10 @@ export { AspectRatioControl } from './controls/aspect-ratio-control';
19
20
  export { SvgMediaControl } from './controls/svg-media-control';
20
21
  export { BackgroundControl } from './controls/background-control/background-control';
21
22
  export { SwitchControl } from './controls/switch-control';
23
+ export { RepeatableControl } from './controls/repeatable-control';
24
+ export { KeyValueControl } from './controls/key-value-control';
25
+ export { PositionControl } from './controls/position-control';
26
+ export { PopoverContent } from './components/popover-content';
22
27
 
23
28
  // components
24
29
  export { ControlFormLabel } from './components/control-form-label';
@@ -32,7 +37,7 @@ export type { EqualUnequalItems } from './controls/equal-unequal-sizes-control';
32
37
  export type { ControlActionsItems } from './control-actions/control-actions-context';
33
38
  export type { PropProviderProps } from './bound-prop-context';
34
39
  export type { SetValue } from './bound-prop-context/prop-context';
35
- export type { ExtendedOption } from './utils/size-control';
40
+ export type { ExtendedOption, Unit } from './utils/size-control';
36
41
  export type { ToggleControlProps } from './controls/toggle-control';
37
42
  export type { FontCategory } from './controls/font-family-control/font-family-control';
38
43