@elementor/editor-controls 1.5.0 → 3.32.0-21
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/CHANGELOG.md +0 -22
- package/dist/index.d.mts +95 -25
- package/dist/index.d.ts +95 -25
- package/dist/index.js +2045 -1041
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1962 -964
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -18
- package/src/components/control-toggle-button-group.tsx +78 -14
- package/src/components/floating-bar.tsx +45 -0
- package/src/components/{font-family-selector.tsx → item-selector.tsx} +62 -50
- package/src/components/repeater.tsx +1 -1
- package/src/components/restricted-link-infotip.tsx +76 -0
- package/src/components/size-control/size-input.tsx +8 -7
- package/src/components/size-control/text-field-inner-selection.tsx +60 -14
- package/src/components/text-field-popover.tsx +30 -7
- package/src/components/unstable-repeater/actions/add-item-action.tsx +50 -0
- package/src/components/unstable-repeater/actions/disable-item-action.tsx +39 -0
- package/src/components/unstable-repeater/actions/duplicate-item-action.tsx +32 -0
- package/src/components/unstable-repeater/actions/remove-item-action.tsx +27 -0
- package/src/components/unstable-repeater/context/repeater-context.tsx +137 -0
- package/src/components/unstable-repeater/header/header.tsx +23 -0
- package/src/components/unstable-repeater/index.ts +5 -0
- package/src/components/unstable-repeater/items/edit-item-popover.tsx +28 -0
- package/src/components/unstable-repeater/items/item.tsx +71 -0
- package/src/components/unstable-repeater/items/items-container.tsx +49 -0
- package/src/components/unstable-repeater/items/use-popover.tsx +26 -0
- package/src/{locations.ts → components/unstable-repeater/locations.ts} +9 -1
- package/src/components/unstable-repeater/types.ts +26 -0
- package/src/components/unstable-repeater/unstable-repeater.tsx +24 -0
- package/src/control-actions/control-actions.tsx +3 -20
- package/src/control-replacements.tsx +41 -0
- package/src/controls/background-control/background-control.tsx +1 -8
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +17 -16
- package/src/controls/equal-unequal-sizes-control.tsx +2 -9
- package/src/controls/filter-control/drop-shadow-item-content.tsx +4 -6
- package/src/controls/filter-control/drop-shadow-item-label.tsx +2 -2
- package/src/controls/filter-repeater-control.tsx +149 -110
- package/src/controls/font-family-control/font-family-control.tsx +22 -10
- package/src/controls/key-value-control.tsx +9 -6
- package/src/controls/link-control.tsx +8 -91
- package/src/controls/linked-dimensions-control.tsx +3 -16
- package/src/controls/number-control.tsx +10 -1
- package/src/controls/position-control.tsx +4 -16
- package/src/controls/repeatable-control.tsx +8 -5
- package/src/controls/select-control.tsx +7 -2
- package/src/controls/selection-size-control.tsx +74 -0
- package/src/controls/size-control.tsx +181 -126
- package/src/controls/stroke-control.tsx +2 -2
- package/src/controls/toggle-control.tsx +3 -2
- package/src/controls/transform-control/functions/axis-row.tsx +4 -2
- package/src/controls/transform-control/functions/move.tsx +2 -1
- package/src/controls/transform-control/functions/rotate.tsx +48 -0
- package/src/controls/transform-control/functions/scale-axis-row.tsx +32 -0
- package/src/controls/transform-control/functions/scale.tsx +45 -0
- package/src/controls/transform-control/functions/skew.tsx +43 -0
- package/src/controls/transform-control/transform-content.tsx +60 -23
- package/src/controls/transform-control/transform-icon.tsx +10 -2
- package/src/controls/transform-control/transform-label.tsx +39 -2
- package/src/controls/transform-control/transform-repeater-control.tsx +2 -10
- package/src/controls/transform-control/types.ts +58 -0
- package/src/controls/transform-control/use-transform-tabs-history.tsx +107 -0
- package/src/controls/transition-control/data.ts +34 -0
- package/src/controls/transition-control/transition-repeater-control.tsx +63 -0
- package/src/controls/transition-control/transition-selector.tsx +88 -0
- package/src/controls/unstable-transform-control/unstable-transform-repeater-control.tsx +35 -0
- package/src/hooks/use-filtered-items-list.ts +24 -0
- package/src/hooks/use-size-extended-options.ts +1 -6
- package/src/index.ts +13 -3
- package/src/utils/size-control.ts +12 -6
- package/src/hooks/use-filtered-font-families.ts +0 -24
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { type RefObject, useRef } from 'react';
|
|
3
3
|
import { dimensionsPropTypeUtil, type PropKey, sizePropTypeUtil } from '@elementor/editor-props';
|
|
4
|
-
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
5
4
|
import { DetachIcon, LinkIcon, SideBottomIcon, SideLeftIcon, SideRightIcon, SideTopIcon } from '@elementor/icons';
|
|
6
5
|
import { Grid, Stack, ToggleButton, Tooltip } from '@elementor/ui';
|
|
7
6
|
import { __ } from '@wordpress/i18n';
|
|
@@ -35,8 +34,6 @@ export const LinkedDimensionsControl = createControl(
|
|
|
35
34
|
|
|
36
35
|
const isLinked = ! dimensionsValue && ! sizeValue ? true : !! sizeValue;
|
|
37
36
|
|
|
38
|
-
const isUsingNestedProps = isExperimentActive( 'e_v_3_30' );
|
|
39
|
-
|
|
40
37
|
const onLinkToggle = () => {
|
|
41
38
|
if ( ! isLinked ) {
|
|
42
39
|
setSizeValue( dimensionsValue[ 'block-start' ]?.value ?? null );
|
|
@@ -71,11 +68,7 @@ export const LinkedDimensionsControl = createControl(
|
|
|
71
68
|
isDisabled={ () => disabled }
|
|
72
69
|
>
|
|
73
70
|
<Stack direction="row" gap={ 2 } flexWrap="nowrap">
|
|
74
|
-
{
|
|
75
|
-
<ControlFormLabel>{ label }</ControlFormLabel>
|
|
76
|
-
) : (
|
|
77
|
-
<ControlLabel>{ label }</ControlLabel>
|
|
78
|
-
) }
|
|
71
|
+
<ControlFormLabel>{ label }</ControlFormLabel>
|
|
79
72
|
<Tooltip title={ isLinked ? unlinkedLabel : linkedLabel } placement="top">
|
|
80
73
|
<ToggleButton
|
|
81
74
|
aria-label={ isLinked ? unlinkedLabel : linkedLabel }
|
|
@@ -91,7 +84,7 @@ export const LinkedDimensionsControl = createControl(
|
|
|
91
84
|
</Tooltip>
|
|
92
85
|
</Stack>
|
|
93
86
|
|
|
94
|
-
{
|
|
87
|
+
{ getCssDimensionProps( isSiteRtl ).map( ( row, index ) => (
|
|
95
88
|
<Stack direction="row" gap={ 2 } flexWrap="nowrap" key={ index } ref={ gridRowRefs[ index ] }>
|
|
96
89
|
{ row.map( ( { icon, ...props } ) => (
|
|
97
90
|
<Grid container gap={ 0.75 } alignItems="center" key={ props.bind }>
|
|
@@ -141,12 +134,6 @@ const Control = ( {
|
|
|
141
134
|
};
|
|
142
135
|
|
|
143
136
|
const Label = ( { label, bind }: { label: string; bind: PropKey } ) => {
|
|
144
|
-
const isUsingNestedProps = isExperimentActive( 'e_v_3_30' );
|
|
145
|
-
|
|
146
|
-
if ( ! isUsingNestedProps ) {
|
|
147
|
-
return <ControlFormLabel>{ label }</ControlFormLabel>;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
137
|
return (
|
|
151
138
|
<PropKeyProvider bind={ bind }>
|
|
152
139
|
<ControlLabel>{ label }</ControlLabel>
|
|
@@ -154,7 +141,7 @@ const Label = ( { label, bind }: { label: string; bind: PropKey } ) => {
|
|
|
154
141
|
);
|
|
155
142
|
};
|
|
156
143
|
|
|
157
|
-
function
|
|
144
|
+
function getCssDimensionProps( isSiteRtl: boolean ) {
|
|
158
145
|
return [
|
|
159
146
|
[
|
|
160
147
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { numberPropTypeUtil } from '@elementor/editor-props';
|
|
3
|
-
import { TextField } from '@elementor/ui';
|
|
3
|
+
import { InputAdornment, TextField } from '@elementor/ui';
|
|
4
4
|
|
|
5
5
|
import { useBoundProp } from '../bound-prop-context';
|
|
6
6
|
import ControlActions from '../control-actions/control-actions';
|
|
@@ -18,12 +18,14 @@ export const NumberControl = createControl(
|
|
|
18
18
|
min = -Number.MAX_VALUE,
|
|
19
19
|
step = 1,
|
|
20
20
|
shouldForceInt = false,
|
|
21
|
+
startIcon,
|
|
21
22
|
}: {
|
|
22
23
|
placeholder?: string;
|
|
23
24
|
max?: number;
|
|
24
25
|
min?: number;
|
|
25
26
|
step?: number;
|
|
26
27
|
shouldForceInt?: boolean;
|
|
28
|
+
startIcon?: React.ReactNode;
|
|
27
29
|
} ) => {
|
|
28
30
|
const { value, setValue, placeholder, disabled } = useBoundProp( numberPropTypeUtil );
|
|
29
31
|
|
|
@@ -52,6 +54,13 @@ export const NumberControl = createControl(
|
|
|
52
54
|
onChange={ handleChange }
|
|
53
55
|
placeholder={ labelPlaceholder ?? ( placeholder ? String( placeholder ) : '' ) }
|
|
54
56
|
inputProps={ { step } }
|
|
57
|
+
InputProps={ {
|
|
58
|
+
startAdornment: startIcon ? (
|
|
59
|
+
<InputAdornment position="start" disabled={ disabled }>
|
|
60
|
+
{ startIcon }
|
|
61
|
+
</InputAdornment>
|
|
62
|
+
) : undefined,
|
|
63
|
+
} }
|
|
55
64
|
onKeyDown={ ( event: KeyboardEvent ) => {
|
|
56
65
|
if ( RESTRICTED_INPUT_KEYS.includes( event.key ) ) {
|
|
57
66
|
event.preventDefault();
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { useMemo } from 'react';
|
|
3
2
|
import { positionPropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
|
|
4
3
|
import { MenuListItem } from '@elementor/editor-ui';
|
|
5
|
-
import { isExperimentActive } from '@elementor/editor-v1-adapters';
|
|
6
4
|
import { LetterXIcon, LetterYIcon } from '@elementor/icons';
|
|
7
5
|
import { Grid, Select, type SelectChangeEvent } from '@elementor/ui';
|
|
8
6
|
import { __ } from '@wordpress/i18n';
|
|
@@ -33,29 +31,19 @@ const positionOptions = [
|
|
|
33
31
|
{ label: __( 'Bottom center', 'elementor' ), value: 'bottom center' },
|
|
34
32
|
{ label: __( 'Bottom left', 'elementor' ), value: 'bottom left' },
|
|
35
33
|
{ label: __( 'Bottom right', 'elementor' ), value: 'bottom right' },
|
|
34
|
+
{ label: __( 'Custom', 'elementor' ), value: 'custom' },
|
|
36
35
|
];
|
|
37
36
|
|
|
38
37
|
export const PositionControl = () => {
|
|
39
38
|
const positionContext = useBoundProp( positionPropTypeUtil );
|
|
40
39
|
const stringPropContext = useBoundProp( stringPropTypeUtil );
|
|
41
40
|
|
|
42
|
-
const
|
|
43
|
-
const isCustom = !! positionContext.value && isVersion331Active;
|
|
44
|
-
|
|
45
|
-
const availablePositionOptions = useMemo( () => {
|
|
46
|
-
const options = [ ...positionOptions ];
|
|
47
|
-
|
|
48
|
-
if ( isVersion331Active ) {
|
|
49
|
-
options.push( { label: __( 'Custom', 'elementor' ), value: 'custom' } );
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return options;
|
|
53
|
-
}, [ isVersion331Active ] );
|
|
41
|
+
const isCustom = !! positionContext.value;
|
|
54
42
|
|
|
55
43
|
const handlePositionChange = ( event: SelectChangeEvent< Positions > ) => {
|
|
56
44
|
const value = event.target.value || null;
|
|
57
45
|
|
|
58
|
-
if ( value === 'custom'
|
|
46
|
+
if ( value === 'custom' ) {
|
|
59
47
|
positionContext.setValue( { x: null, y: null } );
|
|
60
48
|
} else {
|
|
61
49
|
stringPropContext.setValue( value );
|
|
@@ -77,7 +65,7 @@ export const PositionControl = () => {
|
|
|
77
65
|
onChange={ handlePositionChange }
|
|
78
66
|
fullWidth
|
|
79
67
|
>
|
|
80
|
-
{
|
|
68
|
+
{ positionOptions.map( ( { label, value } ) => (
|
|
81
69
|
<MenuListItem key={ value } value={ value ?? '' }>
|
|
82
70
|
{ label }
|
|
83
71
|
</MenuListItem>
|
|
@@ -23,6 +23,7 @@ type RepeatableControlProps = {
|
|
|
23
23
|
initialValues?: object;
|
|
24
24
|
patternLabel?: string;
|
|
25
25
|
placeholder?: string;
|
|
26
|
+
propKey?: string;
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
const PLACEHOLDER_REGEX = /\$\{([^}]+)\}/g;
|
|
@@ -36,6 +37,7 @@ export const RepeatableControl = createControl(
|
|
|
36
37
|
initialValues,
|
|
37
38
|
patternLabel,
|
|
38
39
|
placeholder,
|
|
40
|
+
propKey,
|
|
39
41
|
}: RepeatableControlProps ) => {
|
|
40
42
|
const { propTypeUtil: childPropTypeUtil } = childControlConfig;
|
|
41
43
|
|
|
@@ -44,8 +46,8 @@ export const RepeatableControl = createControl(
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
const childArrayPropTypeUtil = useMemo(
|
|
47
|
-
() => createArrayPropUtils( childPropTypeUtil.key, childPropTypeUtil.schema ),
|
|
48
|
-
[ childPropTypeUtil.key, childPropTypeUtil.schema ]
|
|
49
|
+
() => createArrayPropUtils( childPropTypeUtil.key, childPropTypeUtil.schema, propKey ),
|
|
50
|
+
[ childPropTypeUtil.key, childPropTypeUtil.schema, propKey ]
|
|
49
51
|
);
|
|
50
52
|
|
|
51
53
|
const contextValue = useMemo(
|
|
@@ -181,11 +183,12 @@ const shouldShowPlaceholder = ( pattern: string, data: Record< string, unknown >
|
|
|
181
183
|
|
|
182
184
|
const ItemLabel = ( { value }: { value: Record< string, unknown > } ) => {
|
|
183
185
|
const { placeholder, patternLabel } = useRepeatableControlContext();
|
|
184
|
-
|
|
185
|
-
const label =
|
|
186
|
+
const showPlaceholder = shouldShowPlaceholder( patternLabel, value );
|
|
187
|
+
const label = showPlaceholder ? placeholder : interpolate( patternLabel, value );
|
|
188
|
+
const color = showPlaceholder ? 'text.tertiary' : 'text.primary';
|
|
186
189
|
|
|
187
190
|
return (
|
|
188
|
-
<Box component="span" color=
|
|
191
|
+
<Box component="span" color={ color }>
|
|
189
192
|
{ label }
|
|
190
193
|
</Box>
|
|
191
194
|
);
|
|
@@ -7,14 +7,19 @@ import { useBoundProp } from '../bound-prop-context';
|
|
|
7
7
|
import ControlActions from '../control-actions/control-actions';
|
|
8
8
|
import { createControl } from '../create-control';
|
|
9
9
|
|
|
10
|
+
export type SelectOption = {
|
|
11
|
+
label: string;
|
|
12
|
+
value: StringPropValue[ 'value' ];
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
10
16
|
type Props = {
|
|
11
|
-
options:
|
|
17
|
+
options: SelectOption[];
|
|
12
18
|
onChange?: ( newValue: string | null, previousValue: string | null | undefined ) => void;
|
|
13
19
|
};
|
|
14
20
|
|
|
15
21
|
export const SelectControl = createControl( ( { options, onChange }: Props ) => {
|
|
16
22
|
const { value, setValue, disabled, placeholder } = useBoundProp( stringPropTypeUtil );
|
|
17
|
-
|
|
18
23
|
const handleChange = ( event: SelectChangeEvent< StringPropValue[ 'value' ] > ) => {
|
|
19
24
|
const newValue = event.target.value || null;
|
|
20
25
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useMemo, useRef } from 'react';
|
|
3
|
+
import { selectionSizePropTypeUtil } from '@elementor/editor-props';
|
|
4
|
+
import { Grid } from '@elementor/ui';
|
|
5
|
+
|
|
6
|
+
import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
|
|
7
|
+
import { ControlFormLabel } from '../components/control-form-label';
|
|
8
|
+
import { createControl } from '../create-control';
|
|
9
|
+
import { SizeControl, type SizeControlProps } from './size-control';
|
|
10
|
+
|
|
11
|
+
type SelectionComponentConfig = {
|
|
12
|
+
component: React.ComponentType< Record< string, unknown > >;
|
|
13
|
+
props: Record< string, unknown >;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type SizeControlConfig = Pick< SizeControlProps, 'variant' | 'units' | 'defaultUnit' >;
|
|
17
|
+
|
|
18
|
+
type SelectionSizeControlProps = {
|
|
19
|
+
selectionLabel: string;
|
|
20
|
+
sizeLabel: string;
|
|
21
|
+
selectionConfig: SelectionComponentConfig;
|
|
22
|
+
sizeConfigMap: Record< string, SizeControlConfig >;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const SelectionSizeControl = createControl(
|
|
26
|
+
( { selectionLabel, sizeLabel, selectionConfig, sizeConfigMap }: SelectionSizeControlProps ) => {
|
|
27
|
+
const { value, setValue, propType } = useBoundProp( selectionSizePropTypeUtil );
|
|
28
|
+
const rowRef = useRef< HTMLDivElement >( null );
|
|
29
|
+
|
|
30
|
+
const currentSizeConfig = useMemo( () => {
|
|
31
|
+
switch ( value.selection.$$type ) {
|
|
32
|
+
case 'key-value':
|
|
33
|
+
return sizeConfigMap[ value?.selection?.value.value.value || '' ];
|
|
34
|
+
case 'string':
|
|
35
|
+
return sizeConfigMap[ value?.selection?.value || '' ];
|
|
36
|
+
default:
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}, [ value, sizeConfigMap ] );
|
|
40
|
+
const SelectionComponent = selectionConfig.component;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<PropProvider value={ value } setValue={ setValue } propType={ propType }>
|
|
44
|
+
<Grid container spacing={ 1.5 } ref={ rowRef }>
|
|
45
|
+
<Grid item xs={ 6 } sx={ { display: 'flex', alignItems: 'center' } }>
|
|
46
|
+
<ControlFormLabel>{ selectionLabel }</ControlFormLabel>
|
|
47
|
+
</Grid>
|
|
48
|
+
<Grid item xs={ 6 }>
|
|
49
|
+
<PropKeyProvider bind="selection">
|
|
50
|
+
<SelectionComponent { ...selectionConfig.props } />
|
|
51
|
+
</PropKeyProvider>
|
|
52
|
+
</Grid>
|
|
53
|
+
{ currentSizeConfig && (
|
|
54
|
+
<>
|
|
55
|
+
<Grid item xs={ 6 } sx={ { display: 'flex', alignItems: 'center' } }>
|
|
56
|
+
<ControlFormLabel>{ sizeLabel }</ControlFormLabel>
|
|
57
|
+
</Grid>
|
|
58
|
+
<Grid item xs={ 6 }>
|
|
59
|
+
<PropKeyProvider bind="size">
|
|
60
|
+
<SizeControl
|
|
61
|
+
anchorRef={ rowRef }
|
|
62
|
+
variant={ currentSizeConfig.variant }
|
|
63
|
+
units={ currentSizeConfig.units }
|
|
64
|
+
defaultUnit={ currentSizeConfig.defaultUnit }
|
|
65
|
+
/>
|
|
66
|
+
</PropKeyProvider>
|
|
67
|
+
</Grid>
|
|
68
|
+
</>
|
|
69
|
+
) }
|
|
70
|
+
</Grid>
|
|
71
|
+
</PropProvider>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
);
|
|
@@ -11,165 +11,218 @@ import { createControl } from '../create-control';
|
|
|
11
11
|
import { useSizeExtendedOptions } from '../hooks/use-size-extended-options';
|
|
12
12
|
import { useSyncExternalState } from '../hooks/use-sync-external-state';
|
|
13
13
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
type AngleUnit,
|
|
15
|
+
angleUnits,
|
|
16
|
+
DEFAULT_SIZE,
|
|
17
|
+
DEFAULT_UNIT,
|
|
16
18
|
type ExtendedOption,
|
|
17
19
|
isUnitExtendedOption,
|
|
20
|
+
type LengthUnit,
|
|
21
|
+
lengthUnits,
|
|
22
|
+
type TimeUnit,
|
|
23
|
+
timeUnits,
|
|
18
24
|
type Unit,
|
|
19
25
|
} from '../utils/size-control';
|
|
20
26
|
|
|
21
|
-
const DEFAULT_UNIT = 'px';
|
|
22
|
-
const DEFAULT_SIZE = NaN;
|
|
23
|
-
|
|
24
27
|
type SizeValue = SizePropValue[ 'value' ];
|
|
25
28
|
|
|
26
|
-
type
|
|
29
|
+
type SizeVariant = 'length' | 'angle' | 'time';
|
|
30
|
+
|
|
31
|
+
type UnitProps< T extends readonly Unit[] > = {
|
|
32
|
+
units?: T;
|
|
33
|
+
defaultUnit?: T[ number ];
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type BaseSizeControlProps = {
|
|
27
37
|
placeholder?: string;
|
|
28
38
|
startIcon?: React.ReactNode;
|
|
29
|
-
units?: ( Unit | DegreeUnit )[];
|
|
30
39
|
extendedOptions?: ExtendedOption[];
|
|
31
40
|
disableCustom?: boolean;
|
|
32
41
|
anchorRef?: RefObject< HTMLDivElement | null >;
|
|
33
|
-
defaultUnit?: Unit | DegreeUnit;
|
|
34
42
|
};
|
|
35
43
|
|
|
44
|
+
type LengthSizeControlProps = BaseSizeControlProps &
|
|
45
|
+
UnitProps< LengthUnit[] > & {
|
|
46
|
+
variant: 'length';
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type AngleSizeControlProps = BaseSizeControlProps &
|
|
50
|
+
UnitProps< AngleUnit[] > & {
|
|
51
|
+
variant: 'angle';
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type TimeSizeControlProps = BaseSizeControlProps &
|
|
55
|
+
UnitProps< TimeUnit[] > & {
|
|
56
|
+
variant: 'time';
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type SizeControlProps = LengthSizeControlProps | AngleSizeControlProps | TimeSizeControlProps;
|
|
60
|
+
|
|
36
61
|
type State = {
|
|
37
62
|
numeric: number;
|
|
38
63
|
custom: string;
|
|
39
|
-
unit: Unit |
|
|
64
|
+
unit: Unit | ExtendedOption;
|
|
40
65
|
};
|
|
41
66
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const defaultSelectedUnit: Record< SizeControlProps[ 'variant' ], Unit > = {
|
|
68
|
+
length: 'px',
|
|
69
|
+
angle: 'deg',
|
|
70
|
+
time: 'ms',
|
|
71
|
+
} as const;
|
|
72
|
+
|
|
73
|
+
const defaultUnits: Record< SizeControlProps[ 'variant' ], Unit[] > = {
|
|
74
|
+
length: [ ...lengthUnits ] as LengthUnit[],
|
|
75
|
+
angle: [ ...angleUnits ] as AngleUnit[],
|
|
76
|
+
time: [ ...timeUnits ] as TimeUnit[],
|
|
77
|
+
} as const;
|
|
78
|
+
|
|
79
|
+
export const SizeControl = createControl(
|
|
80
|
+
( {
|
|
81
|
+
variant = 'length' as SizeControlProps[ 'variant' ],
|
|
82
|
+
defaultUnit,
|
|
83
|
+
units,
|
|
84
|
+
placeholder,
|
|
85
|
+
startIcon,
|
|
86
|
+
anchorRef,
|
|
87
|
+
extendedOptions,
|
|
88
|
+
disableCustom,
|
|
89
|
+
}: Omit< SizeControlProps, 'variant' > & { variant?: SizeVariant } ) => {
|
|
90
|
+
const {
|
|
91
|
+
value: sizeValue,
|
|
92
|
+
setValue: setSizeValue,
|
|
93
|
+
disabled,
|
|
94
|
+
restoreValue,
|
|
95
|
+
placeholder: externalPlaceholder,
|
|
96
|
+
} = useBoundProp( sizePropTypeUtil );
|
|
97
|
+
const actualDefaultUnit = defaultUnit ?? externalPlaceholder?.unit ?? defaultSelectedUnit[ variant ];
|
|
98
|
+
const actualUnits = units ?? [ ...defaultUnits[ variant ] ];
|
|
99
|
+
const [ internalState, setInternalState ] = useState( createStateFromSizeProp( sizeValue, actualDefaultUnit ) );
|
|
100
|
+
const activeBreakpoint = useActiveBreakpoint();
|
|
101
|
+
|
|
102
|
+
const actualExtendedOptions = useSizeExtendedOptions( extendedOptions || [], disableCustom ?? false );
|
|
103
|
+
const popupState = usePopupState( { variant: 'popover' } );
|
|
104
|
+
|
|
105
|
+
const [ state, setState ] = useSyncExternalState( {
|
|
106
|
+
external: internalState,
|
|
107
|
+
setExternal: ( newState: State | null ) => setSizeValue( extractValueFromState( newState ) ),
|
|
108
|
+
persistWhen: ( newState ) => {
|
|
109
|
+
if ( ! newState?.unit ) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if ( isUnitExtendedOption( newState.unit ) ) {
|
|
114
|
+
return newState.unit === 'auto' ? true : !! newState.custom;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return !! newState?.numeric || newState?.numeric === 0;
|
|
118
|
+
},
|
|
119
|
+
fallback: ( newState ) => ( {
|
|
120
|
+
unit: newState?.unit ?? actualDefaultUnit,
|
|
121
|
+
numeric: newState?.numeric ?? DEFAULT_SIZE,
|
|
122
|
+
custom: newState?.custom ?? '',
|
|
123
|
+
} ),
|
|
124
|
+
} );
|
|
125
|
+
|
|
126
|
+
const { size: controlSize = DEFAULT_SIZE, unit: controlUnit = actualDefaultUnit } =
|
|
127
|
+
extractValueFromState( state ) || {};
|
|
128
|
+
|
|
129
|
+
const handleUnitChange = ( newUnit: Unit | ExtendedOption ) => {
|
|
130
|
+
if ( newUnit === 'custom' ) {
|
|
131
|
+
popupState.open( anchorRef?.current );
|
|
62
132
|
}
|
|
63
133
|
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
fallback: ( newState ) => ( {
|
|
67
|
-
unit: newState?.unit ?? defaultUnit,
|
|
68
|
-
numeric: newState?.numeric ?? DEFAULT_SIZE,
|
|
69
|
-
custom: newState?.custom ?? '',
|
|
70
|
-
} ),
|
|
71
|
-
} );
|
|
72
|
-
|
|
73
|
-
const { size: controlSize = DEFAULT_SIZE, unit: controlUnit = defaultUnit } = extractValueFromState( state ) || {};
|
|
74
|
-
|
|
75
|
-
const handleUnitChange = ( newUnit: Unit | DegreeUnit | ExtendedOption ) => {
|
|
76
|
-
if ( newUnit === 'custom' ) {
|
|
77
|
-
popupState.open( anchorRef?.current );
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
setState( ( prev ) => ( { ...prev, unit: newUnit } ) );
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const handleSizeChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
84
|
-
const { value: size } = event.target;
|
|
85
|
-
|
|
86
|
-
if ( controlUnit === 'auto' ) {
|
|
87
|
-
setState( ( prev ) => ( { ...prev, unit: controlUnit } ) );
|
|
134
|
+
setState( ( prev ) => ( { ...prev, unit: newUnit } ) );
|
|
135
|
+
};
|
|
88
136
|
|
|
89
|
-
|
|
90
|
-
|
|
137
|
+
const handleSizeChange = ( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
138
|
+
const { value: size } = event.target;
|
|
91
139
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
[ controlUnit === 'custom' ? 'custom' : 'numeric' ]: formatSize( size, controlUnit ),
|
|
95
|
-
unit: controlUnit,
|
|
96
|
-
} ) );
|
|
97
|
-
};
|
|
140
|
+
if ( controlUnit === 'auto' ) {
|
|
141
|
+
setState( ( prev ) => ( { ...prev, unit: controlUnit } ) );
|
|
98
142
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
( event.target as HTMLElement )?.blur();
|
|
102
|
-
}
|
|
103
|
-
};
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
104
145
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
146
|
+
setState( ( prev ) => ( {
|
|
147
|
+
...prev,
|
|
148
|
+
[ controlUnit === 'custom' ? 'custom' : 'numeric' ]: formatSize( size, controlUnit ),
|
|
149
|
+
unit: controlUnit,
|
|
150
|
+
} ) );
|
|
151
|
+
};
|
|
110
152
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
...state,
|
|
116
|
-
unit: newState.unit ?? state.unit,
|
|
117
|
-
[ currentUnitType ]: newState[ currentUnitType ],
|
|
153
|
+
const onInputClick = ( event: React.MouseEvent ) => {
|
|
154
|
+
if ( ( event.target as HTMLElement ).closest( 'input' ) && 'custom' === state.unit ) {
|
|
155
|
+
popupState.open( anchorRef?.current );
|
|
156
|
+
}
|
|
118
157
|
};
|
|
119
158
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
159
|
+
useEffect( () => {
|
|
160
|
+
const newState = createStateFromSizeProp(
|
|
161
|
+
sizeValue,
|
|
162
|
+
state.unit === 'custom' ? state.unit : actualDefaultUnit,
|
|
163
|
+
'',
|
|
164
|
+
state.custom
|
|
165
|
+
);
|
|
166
|
+
const currentUnitType = isUnitExtendedOption( state.unit ) ? 'custom' : 'numeric';
|
|
167
|
+
const mergedStates = {
|
|
168
|
+
...state,
|
|
169
|
+
unit: newState.unit ?? state.unit,
|
|
170
|
+
[ currentUnitType ]: newState[ currentUnitType ],
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if ( mergedStates.unit !== 'auto' && areStatesEqual( state, mergedStates ) ) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
123
176
|
|
|
124
|
-
|
|
125
|
-
|
|
177
|
+
if ( state.unit === newState.unit ) {
|
|
178
|
+
setInternalState( mergedStates );
|
|
126
179
|
|
|
127
|
-
|
|
128
|
-
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
129
182
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
183
|
+
setState( newState );
|
|
184
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
185
|
+
}, [ sizeValue ] );
|
|
133
186
|
|
|
134
|
-
|
|
135
|
-
|
|
187
|
+
useEffect( () => {
|
|
188
|
+
const newState = createStateFromSizeProp( sizeValue, actualDefaultUnit, '', state.custom );
|
|
136
189
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
onClick={ onInputClick }
|
|
157
|
-
popupState={ popupState }
|
|
158
|
-
/>
|
|
159
|
-
{ anchorRef?.current && (
|
|
160
|
-
<TextFieldPopover
|
|
190
|
+
if ( activeBreakpoint && ! areStatesEqual( newState, state ) ) {
|
|
191
|
+
setState( newState );
|
|
192
|
+
}
|
|
193
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
194
|
+
}, [ activeBreakpoint ] );
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<>
|
|
198
|
+
<SizeInput
|
|
199
|
+
disabled={ disabled }
|
|
200
|
+
size={ controlSize }
|
|
201
|
+
unit={ controlUnit }
|
|
202
|
+
units={ [ ...actualUnits, ...( actualExtendedOptions || [] ) ] }
|
|
203
|
+
placeholder={ placeholder }
|
|
204
|
+
startIcon={ startIcon }
|
|
205
|
+
handleSizeChange={ handleSizeChange }
|
|
206
|
+
handleUnitChange={ handleUnitChange }
|
|
207
|
+
onBlur={ restoreValue }
|
|
208
|
+
onClick={ onInputClick }
|
|
161
209
|
popupState={ popupState }
|
|
162
|
-
anchorRef={ anchorRef }
|
|
163
|
-
restoreValue={ restoreValue }
|
|
164
|
-
value={ controlSize as string }
|
|
165
|
-
onChange={ handleSizeChange }
|
|
166
210
|
/>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
211
|
+
{ anchorRef?.current && (
|
|
212
|
+
<TextFieldPopover
|
|
213
|
+
popupState={ popupState }
|
|
214
|
+
anchorRef={ anchorRef }
|
|
215
|
+
restoreValue={ restoreValue }
|
|
216
|
+
value={ controlSize as string }
|
|
217
|
+
onChange={ handleSizeChange }
|
|
218
|
+
/>
|
|
219
|
+
) }
|
|
220
|
+
</>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
);
|
|
171
224
|
|
|
172
|
-
function formatSize< TSize extends string | number >( size: TSize, unit: Unit |
|
|
225
|
+
function formatSize< TSize extends string | number >( size: TSize, unit: Unit | ExtendedOption ): TSize {
|
|
173
226
|
if ( isUnitExtendedOption( unit ) ) {
|
|
174
227
|
return unit === 'auto' ? ( '' as TSize ) : ( String( size ?? '' ) as TSize );
|
|
175
228
|
}
|
|
@@ -179,17 +232,19 @@ function formatSize< TSize extends string | number >( size: TSize, unit: Unit |
|
|
|
179
232
|
|
|
180
233
|
function createStateFromSizeProp(
|
|
181
234
|
sizeValue: SizeValue | null,
|
|
182
|
-
defaultUnit: Unit |
|
|
235
|
+
defaultUnit: Unit | ExtendedOption,
|
|
236
|
+
defaultSize: string | number = '',
|
|
237
|
+
customState: string = ''
|
|
183
238
|
): State {
|
|
184
239
|
const unit = sizeValue?.unit ?? defaultUnit;
|
|
185
|
-
const size = sizeValue?.size ??
|
|
240
|
+
const size = sizeValue?.size ?? defaultSize;
|
|
186
241
|
|
|
187
242
|
return {
|
|
188
243
|
numeric:
|
|
189
244
|
! isUnitExtendedOption( unit ) && ! isNaN( Number( size ) ) && ( size || size === 0 )
|
|
190
245
|
? Number( size )
|
|
191
246
|
: DEFAULT_SIZE,
|
|
192
|
-
custom: unit === 'custom' ? String( size ) :
|
|
247
|
+
custom: unit === 'custom' ? String( size ) : customState,
|
|
193
248
|
unit,
|
|
194
249
|
};
|
|
195
250
|
}
|
|
@@ -8,7 +8,7 @@ import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-conte
|
|
|
8
8
|
import { ControlFormLabel } from '../components/control-form-label';
|
|
9
9
|
import { SectionContent } from '../components/section-content';
|
|
10
10
|
import { createControl } from '../create-control';
|
|
11
|
-
import { type
|
|
11
|
+
import { type LengthUnit } from '../utils/size-control';
|
|
12
12
|
import { ColorControl } from './color-control';
|
|
13
13
|
import { SizeControl } from './size-control';
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ type StrokeProps = {
|
|
|
18
18
|
children: React.ReactNode;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
const units:
|
|
21
|
+
const units: LengthUnit[] = [ 'px', 'em', 'rem' ];
|
|
22
22
|
|
|
23
23
|
export const StrokeControl = createControl( () => {
|
|
24
24
|
const propContext = useBoundProp( strokePropTypeUtil );
|