@elementor/editor-controls 0.15.0 → 0.17.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.
- package/CHANGELOG.md +45 -0
- package/dist/index.d.mts +19 -6
- package/dist/index.d.ts +19 -6
- package/dist/index.js +478 -178
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +484 -165
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -6
- package/src/components/control-toggle-button-group.tsx +5 -0
- package/src/components/enable-unfiltered-modal.tsx +130 -0
- package/src/components/repeater.tsx +39 -10
- package/src/components/sortable.tsx +2 -7
- package/src/controls/background-control/background-gradient-color-control.tsx +101 -0
- package/src/controls/background-control/background-overlay/background-overlay-repeater-control.tsx +80 -14
- package/src/controls/background-control/background-overlay/use-background-tabs-history.ts +29 -2
- package/src/controls/box-shadow-repeater-control.tsx +15 -4
- package/src/controls/equal-unequal-sizes-control.tsx +1 -1
- package/src/controls/font-family-control/font-family-control.tsx +35 -14
- package/src/controls/link-control.tsx +11 -2
- package/src/controls/svg-media-control.tsx +20 -5
- package/src/controls/toggle-control.tsx +36 -10
- package/src/hooks/use-filtered-font-families.ts +15 -16
- package/src/index.ts +2 -0
- package/src/utils/link-restriction.ts +47 -0
- package/src/utils/types.ts +5 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { boxShadowPropTypeUtil, type PropKey, shadowPropTypeUtil, type ShadowPropValue } from '@elementor/editor-props';
|
|
3
|
-
import { Grid, Typography, UnstableColorIndicator } from '@elementor/ui';
|
|
3
|
+
import { Grid, type SxProps, type Theme, Typography, UnstableColorIndicator } from '@elementor/ui';
|
|
4
4
|
import { __ } from '@wordpress/i18n';
|
|
5
5
|
|
|
6
6
|
import { PropKeyProvider, PropProvider, useBoundProp } from '../bound-prop-context';
|
|
@@ -18,6 +18,7 @@ export const BoxShadowRepeaterControl = createControl( () => {
|
|
|
18
18
|
return (
|
|
19
19
|
<PropProvider propType={ propType } value={ value } setValue={ setValue }>
|
|
20
20
|
<Repeater
|
|
21
|
+
openOnAdd
|
|
21
22
|
values={ value ?? [] }
|
|
22
23
|
setValues={ setValue }
|
|
23
24
|
label={ __( 'Box shadow', 'elementor' ) }
|
|
@@ -68,7 +69,7 @@ const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
|
|
|
68
69
|
} }
|
|
69
70
|
/>
|
|
70
71
|
</Control>
|
|
71
|
-
<Control bind="position" label={ __( 'Position', 'elementor' ) }>
|
|
72
|
+
<Control bind="position" label={ __( 'Position', 'elementor' ) } sx={ { overflow: 'hidden' } }>
|
|
72
73
|
<SelectControl
|
|
73
74
|
options={ [
|
|
74
75
|
{ label: __( 'Inset', 'elementor' ), value: 'inset' },
|
|
@@ -98,9 +99,19 @@ const Content = ( { anchorEl }: { anchorEl: HTMLElement | null } ) => {
|
|
|
98
99
|
);
|
|
99
100
|
};
|
|
100
101
|
|
|
101
|
-
const Control = ( {
|
|
102
|
+
const Control = ( {
|
|
103
|
+
label,
|
|
104
|
+
bind,
|
|
105
|
+
children,
|
|
106
|
+
sx,
|
|
107
|
+
}: {
|
|
108
|
+
bind: string;
|
|
109
|
+
label: string;
|
|
110
|
+
children: React.ReactNode;
|
|
111
|
+
sx?: SxProps< Theme >;
|
|
112
|
+
} ) => (
|
|
102
113
|
<PropKeyProvider bind={ bind }>
|
|
103
|
-
<Grid item xs={ 6 } sx={
|
|
114
|
+
<Grid item xs={ 6 } sx={ sx }>
|
|
104
115
|
<Grid container gap={ 1 } alignItems="center">
|
|
105
116
|
<Grid item xs={ 12 }>
|
|
106
117
|
<Typography component="label" variant="caption" color="text.secondary">
|
|
@@ -144,8 +144,8 @@ export function EqualUnequalSizesControl< TMultiPropType extends string, TPropVa
|
|
|
144
144
|
<MultiSizeValueControl item={ items[ 1 ] } />
|
|
145
145
|
</PopoverGridContainer>
|
|
146
146
|
<PopoverGridContainer>
|
|
147
|
-
<MultiSizeValueControl item={ items[ 3 ] } />
|
|
148
147
|
<MultiSizeValueControl item={ items[ 2 ] } />
|
|
148
|
+
<MultiSizeValueControl item={ items[ 3 ] } />
|
|
149
149
|
</PopoverGridContainer>
|
|
150
150
|
</PopoverContent>
|
|
151
151
|
</PropProvider>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { stringPropTypeUtil } from '@elementor/editor-props';
|
|
4
|
-
import { ChevronDownIcon,
|
|
4
|
+
import { ChevronDownIcon, SearchIcon, TextIcon, XIcon } from '@elementor/icons';
|
|
5
5
|
import {
|
|
6
6
|
bindPopover,
|
|
7
7
|
bindTrigger,
|
|
@@ -31,8 +31,13 @@ import { enqueueFont } from './enqueue-font';
|
|
|
31
31
|
|
|
32
32
|
const SIZE = 'tiny';
|
|
33
33
|
|
|
34
|
+
export type FontCategory = {
|
|
35
|
+
label: string;
|
|
36
|
+
fonts: string[];
|
|
37
|
+
};
|
|
38
|
+
|
|
34
39
|
type FontFamilyControlProps = {
|
|
35
|
-
fontFamilies:
|
|
40
|
+
fontFamilies: FontCategory[];
|
|
36
41
|
};
|
|
37
42
|
|
|
38
43
|
export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyControlProps ) => {
|
|
@@ -72,7 +77,7 @@ export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyCo
|
|
|
72
77
|
>
|
|
73
78
|
<Stack>
|
|
74
79
|
<Stack direction="row" alignItems="center" pl={ 1.5 } pr={ 0.5 } py={ 1.5 }>
|
|
75
|
-
<
|
|
80
|
+
<TextIcon fontSize={ SIZE } sx={ { mr: 0.5 } } />
|
|
76
81
|
<Typography variant="subtitle2">{ __( 'Font Family', 'elementor' ) }</Typography>
|
|
77
82
|
<IconButton size={ SIZE } sx={ { ml: 'auto' } } onClick={ handleClose }>
|
|
78
83
|
<XIcon fontSize={ SIZE } />
|
|
@@ -105,24 +110,40 @@ export const FontFamilyControl = createControl( ( { fontFamilies }: FontFamilyCo
|
|
|
105
110
|
/>
|
|
106
111
|
) : (
|
|
107
112
|
<Box sx={ { overflowY: 'auto', height: 260, width: 220 } }>
|
|
108
|
-
<Stack alignItems="center" p={ 2.5 } gap={ 1.5 }>
|
|
109
|
-
<
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
<Stack alignItems="center" p={ 2.5 } gap={ 1.5 } overflow={ 'hidden' }>
|
|
114
|
+
<TextIcon fontSize="large" />
|
|
115
|
+
<Box sx={ { maxWidth: 160, overflow: 'hidden' } }>
|
|
116
|
+
<Typography align="center" variant="subtitle2" color="text.secondary">
|
|
117
|
+
{ __( 'Sorry, nothing matched', 'elementor' ) }
|
|
118
|
+
</Typography>
|
|
119
|
+
<Typography
|
|
120
|
+
variant="subtitle2"
|
|
121
|
+
color="text.secondary"
|
|
122
|
+
sx={ {
|
|
123
|
+
display: 'flex',
|
|
124
|
+
width: '100%',
|
|
125
|
+
justifyContent: 'center',
|
|
126
|
+
} }
|
|
127
|
+
>
|
|
128
|
+
<span>“</span>
|
|
129
|
+
<span
|
|
130
|
+
style={ { maxWidth: '80%', overflow: 'hidden', textOverflow: 'ellipsis' } }
|
|
131
|
+
>
|
|
132
|
+
{ searchValue }
|
|
133
|
+
</span>
|
|
134
|
+
<span>”.</span>
|
|
135
|
+
</Typography>
|
|
136
|
+
</Box>
|
|
115
137
|
<Typography align="center" variant="caption" color="text.secondary">
|
|
138
|
+
{ __( 'Try something else.', 'elementor' ) }
|
|
116
139
|
<Link
|
|
117
140
|
color="secondary"
|
|
118
141
|
variant="caption"
|
|
119
142
|
component="button"
|
|
120
143
|
onClick={ () => setSearchValue( '' ) }
|
|
121
144
|
>
|
|
122
|
-
{ __( 'Clear
|
|
145
|
+
{ __( 'Clear & try again', 'elementor' ) }
|
|
123
146
|
</Link>
|
|
124
|
-
|
|
125
|
-
{ __( 'and try again.', 'elementor' ) }
|
|
126
147
|
</Typography>
|
|
127
148
|
</Stack>
|
|
128
149
|
</Box>
|
|
@@ -262,7 +283,7 @@ const StyledMenuList = styled( MenuList )( ( { theme } ) => ( {
|
|
|
262
283
|
'& > [role="option"]': {
|
|
263
284
|
...theme.typography.caption,
|
|
264
285
|
lineHeight: 'inherit',
|
|
265
|
-
padding: theme.spacing( 0.75, 2 ),
|
|
286
|
+
padding: theme.spacing( 0.75, 2, 0.75, 4 ),
|
|
266
287
|
'&:hover, &:focus': {
|
|
267
288
|
backgroundColor: theme.palette.action.hover,
|
|
268
289
|
},
|
|
@@ -26,8 +26,10 @@ import {
|
|
|
26
26
|
import { ControlLabel } from '../components/control-label';
|
|
27
27
|
import ControlActions from '../control-actions/control-actions';
|
|
28
28
|
import { createControl } from '../create-control';
|
|
29
|
+
import { getLinkRestriction } from '../utils/link-restriction';
|
|
30
|
+
import { type ControlProps } from '../utils/types';
|
|
29
31
|
|
|
30
|
-
type Props = {
|
|
32
|
+
type Props = ControlProps< {
|
|
31
33
|
queryOptions: {
|
|
32
34
|
requestParams: Record< string, unknown >;
|
|
33
35
|
endpoint: string;
|
|
@@ -35,7 +37,7 @@ type Props = {
|
|
|
35
37
|
allowCustomValues?: boolean;
|
|
36
38
|
minInputLength?: number;
|
|
37
39
|
placeholder?: string;
|
|
38
|
-
}
|
|
40
|
+
} >;
|
|
39
41
|
|
|
40
42
|
type LinkSessionValue = {
|
|
41
43
|
value?: LinkPropValue[ 'value' ] | null;
|
|
@@ -58,6 +60,7 @@ export const LinkControl = createControl( ( props: Props ) => {
|
|
|
58
60
|
queryOptions: { endpoint = '', requestParams = {} },
|
|
59
61
|
placeholder,
|
|
60
62
|
minInputLength = 2,
|
|
63
|
+
context: { elementId },
|
|
61
64
|
} = props || {};
|
|
62
65
|
|
|
63
66
|
const [ options, setOptions ] = useState< FlatOption[] | CategorizedOption[] >(
|
|
@@ -65,6 +68,12 @@ export const LinkControl = createControl( ( props: Props ) => {
|
|
|
65
68
|
);
|
|
66
69
|
|
|
67
70
|
const onEnabledChange = () => {
|
|
71
|
+
const { shouldRestrict } = getLinkRestriction( elementId );
|
|
72
|
+
|
|
73
|
+
if ( shouldRestrict && ! isEnabled ) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
setIsEnabled( ( prevState ) => ! prevState );
|
|
69
78
|
setValue( isEnabled ? null : linkSessionValue?.value ?? null );
|
|
70
79
|
setLinkSessionValue( { value, meta: { isEnabled: ! isEnabled } } );
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import { imageSrcPropTypeUtil } from '@elementor/editor-props';
|
|
3
4
|
import { UploadIcon } from '@elementor/icons';
|
|
4
5
|
import { Button, Card, CardMedia, CardOverlay, CircularProgress, Stack, styled } from '@elementor/ui';
|
|
@@ -7,6 +8,7 @@ import { __ } from '@wordpress/i18n';
|
|
|
7
8
|
|
|
8
9
|
import { useBoundProp } from '../bound-prop-context';
|
|
9
10
|
import { ControlLabel } from '../components/control-label';
|
|
11
|
+
import { EnableUnfilteredModal } from '../components/enable-unfiltered-modal';
|
|
10
12
|
import ControlActions from '../control-actions/control-actions';
|
|
11
13
|
import { createControl } from '../create-control';
|
|
12
14
|
import { useUnfilteredFilesUpload } from '../hooks/use-unfiltered-files-upload';
|
|
@@ -36,12 +38,16 @@ const StyledCardMediaContainer = styled( Stack )`
|
|
|
36
38
|
background-color: rgba( 255, 255, 255, 0.37 );
|
|
37
39
|
`;
|
|
38
40
|
|
|
41
|
+
const MODE_BROWSE: OpenOptions = { mode: 'browse' };
|
|
42
|
+
const MODE_UPLOAD: OpenOptions = { mode: 'upload' };
|
|
43
|
+
|
|
39
44
|
export const SvgMediaControl = createControl( () => {
|
|
40
45
|
const { value, setValue } = useBoundProp( imageSrcPropTypeUtil );
|
|
41
46
|
const { id, url } = value ?? {};
|
|
42
47
|
const { data: attachment, isFetching } = useWpMediaAttachment( id?.value || null );
|
|
43
48
|
const src = attachment?.url ?? url?.value ?? null;
|
|
44
49
|
const { data: allowSvgUpload } = useUnfilteredFilesUpload();
|
|
50
|
+
const [ unfilteredModalOpenState, setUnfilteredModalOpenState ] = useState( false );
|
|
45
51
|
|
|
46
52
|
const { open } = useWpMediaFrame( {
|
|
47
53
|
mediaTypes: [ 'svg' ],
|
|
@@ -58,16 +64,25 @@ export const SvgMediaControl = createControl( () => {
|
|
|
58
64
|
},
|
|
59
65
|
} );
|
|
60
66
|
|
|
67
|
+
const onCloseUnfilteredModal = ( enabled: boolean ) => {
|
|
68
|
+
setUnfilteredModalOpenState( false );
|
|
69
|
+
|
|
70
|
+
if ( enabled ) {
|
|
71
|
+
open( MODE_UPLOAD );
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
61
75
|
const handleClick = ( openOptions?: OpenOptions ) => {
|
|
62
|
-
if ( allowSvgUpload ) {
|
|
63
|
-
|
|
76
|
+
if ( ! allowSvgUpload && openOptions === MODE_UPLOAD ) {
|
|
77
|
+
setUnfilteredModalOpenState( true );
|
|
64
78
|
} else {
|
|
65
|
-
|
|
79
|
+
open( openOptions );
|
|
66
80
|
}
|
|
67
81
|
};
|
|
68
82
|
|
|
69
83
|
return (
|
|
70
84
|
<Stack gap={ 1 }>
|
|
85
|
+
<EnableUnfilteredModal open={ unfilteredModalOpenState } onClose={ onCloseUnfilteredModal } />
|
|
71
86
|
<ControlLabel> { __( 'SVG', 'elementor' ) } </ControlLabel>
|
|
72
87
|
<ControlActions>
|
|
73
88
|
<StyledCard variant="outlined">
|
|
@@ -95,7 +110,7 @@ export const SvgMediaControl = createControl( () => {
|
|
|
95
110
|
size="tiny"
|
|
96
111
|
color="inherit"
|
|
97
112
|
variant="outlined"
|
|
98
|
-
onClick={ () => handleClick(
|
|
113
|
+
onClick={ () => handleClick( MODE_BROWSE ) }
|
|
99
114
|
>
|
|
100
115
|
{ __( 'Select SVG', 'elementor' ) }
|
|
101
116
|
</Button>
|
|
@@ -104,7 +119,7 @@ export const SvgMediaControl = createControl( () => {
|
|
|
104
119
|
variant="text"
|
|
105
120
|
color="inherit"
|
|
106
121
|
startIcon={ <UploadIcon /> }
|
|
107
|
-
onClick={ () => handleClick(
|
|
122
|
+
onClick={ () => handleClick( MODE_UPLOAD ) }
|
|
108
123
|
>
|
|
109
124
|
{ __( 'Upload', 'elementor' ) }
|
|
110
125
|
</Button>
|
|
@@ -6,28 +6,54 @@ import { useBoundProp } from '../bound-prop-context';
|
|
|
6
6
|
import { ControlToggleButtonGroup, type ToggleButtonGroupItem } from '../components/control-toggle-button-group';
|
|
7
7
|
import { createControl } from '../create-control';
|
|
8
8
|
|
|
9
|
-
type ToggleControlProps< T extends PropValue > = {
|
|
10
|
-
options: ToggleButtonGroupItem< T >
|
|
9
|
+
export type ToggleControlProps< T extends PropValue > = {
|
|
10
|
+
options: Array< ToggleButtonGroupItem< T > & { exclusive?: boolean } >;
|
|
11
11
|
fullWidth?: boolean;
|
|
12
12
|
size?: ToggleButtonProps[ 'size' ];
|
|
13
|
+
exclusive?: boolean;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export const ToggleControl = createControl(
|
|
16
|
-
( {
|
|
17
|
+
( {
|
|
18
|
+
options,
|
|
19
|
+
fullWidth = false,
|
|
20
|
+
size = 'tiny',
|
|
21
|
+
exclusive = true,
|
|
22
|
+
}: ToggleControlProps< StringPropValue[ 'value' ] > ) => {
|
|
17
23
|
const { value, setValue } = useBoundProp( stringPropTypeUtil );
|
|
18
24
|
|
|
19
|
-
const
|
|
20
|
-
|
|
25
|
+
const exclusiveValues = options.filter( ( option ) => option.exclusive ).map( ( option ) => option.value );
|
|
26
|
+
|
|
27
|
+
const handleNonExclusiveToggle = ( selectedValues: StringPropValue[ 'value' ][] ) => {
|
|
28
|
+
const newSelectedValue = selectedValues[ selectedValues.length - 1 ];
|
|
29
|
+
const isNewSelectedValueExclusive = exclusiveValues.includes( newSelectedValue );
|
|
30
|
+
|
|
31
|
+
const updatedValues = isNewSelectedValueExclusive
|
|
32
|
+
? [ newSelectedValue ]
|
|
33
|
+
: selectedValues?.filter( ( val ) => ! exclusiveValues.includes( val ) );
|
|
34
|
+
|
|
35
|
+
setValue( updatedValues?.join( ' ' ) || null );
|
|
21
36
|
};
|
|
22
37
|
|
|
23
|
-
|
|
38
|
+
const toggleButtonGroupProps = {
|
|
39
|
+
items: options,
|
|
40
|
+
fullWidth,
|
|
41
|
+
size,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return exclusive ? (
|
|
24
45
|
<ControlToggleButtonGroup
|
|
25
|
-
|
|
46
|
+
{ ...toggleButtonGroupProps }
|
|
26
47
|
value={ value ?? null }
|
|
27
|
-
onChange={
|
|
48
|
+
onChange={ setValue }
|
|
28
49
|
exclusive={ true }
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
/>
|
|
51
|
+
) : (
|
|
52
|
+
<ControlToggleButtonGroup
|
|
53
|
+
{ ...toggleButtonGroupProps }
|
|
54
|
+
value={ value?.split( ' ' ) ?? [] }
|
|
55
|
+
onChange={ handleNonExclusiveToggle }
|
|
56
|
+
exclusive={ false }
|
|
31
57
|
/>
|
|
32
58
|
);
|
|
33
59
|
}
|
|
@@ -1,25 +1,24 @@
|
|
|
1
|
+
import { type FontCategory } from '@elementor/editor-controls';
|
|
2
|
+
|
|
1
3
|
export type FontListItem = {
|
|
2
4
|
type: 'font' | 'category';
|
|
3
5
|
value: string;
|
|
4
6
|
};
|
|
5
7
|
|
|
6
|
-
export const useFilteredFontFamilies = ( fontFamilies:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if ( filteredFonts.length ) {
|
|
12
|
-
acc.push( { type: 'category', value: category } );
|
|
8
|
+
export const useFilteredFontFamilies = ( fontFamilies: FontCategory[], searchValue: string ) => {
|
|
9
|
+
return fontFamilies.reduce< FontListItem[] >( ( acc, category ) => {
|
|
10
|
+
const filteredFonts = category.fonts.filter( ( font ) =>
|
|
11
|
+
font.toLowerCase().includes( searchValue.toLowerCase() )
|
|
12
|
+
);
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} );
|
|
17
|
-
}
|
|
14
|
+
if ( filteredFonts.length ) {
|
|
15
|
+
acc.push( { type: 'category', value: category.label } );
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
filteredFonts.forEach( ( font ) => {
|
|
18
|
+
acc.push( { type: 'font', value: font } );
|
|
19
|
+
} );
|
|
20
|
+
}
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
return acc;
|
|
23
|
+
}, [] );
|
|
25
24
|
};
|
package/src/index.ts
CHANGED
|
@@ -30,6 +30,8 @@ export type { ControlActionsItems } from './control-actions/control-actions-cont
|
|
|
30
30
|
export type { PropProviderProps } from './bound-prop-context';
|
|
31
31
|
export type { SetValue } from './bound-prop-context/prop-context';
|
|
32
32
|
export type { ExtendedValue } from './controls/size-control';
|
|
33
|
+
export type { ToggleControlProps } from './controls/toggle-control';
|
|
34
|
+
export type { FontCategory } from './controls/font-family-control/font-family-control';
|
|
33
35
|
|
|
34
36
|
// providers
|
|
35
37
|
export { createControlReplacement, ControlReplacementProvider } from './create-control-replacement';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getContainer } from '@elementor/editor-elements';
|
|
2
|
+
|
|
3
|
+
type LinkRestriction =
|
|
4
|
+
| {
|
|
5
|
+
shouldRestrict: true;
|
|
6
|
+
restrictReason: 'ancestor' | 'descendant';
|
|
7
|
+
}
|
|
8
|
+
| {
|
|
9
|
+
shouldRestrict: false;
|
|
10
|
+
restrictReason?: never;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function getLinkRestriction( elementId: string ): LinkRestriction {
|
|
14
|
+
if ( getAncestorAnchor( elementId ) ) {
|
|
15
|
+
return {
|
|
16
|
+
shouldRestrict: true,
|
|
17
|
+
restrictReason: 'ancestor',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if ( getDescendantAnchor( elementId ) ) {
|
|
22
|
+
return {
|
|
23
|
+
shouldRestrict: true,
|
|
24
|
+
restrictReason: 'descendant',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { shouldRestrict: false };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getAncestorAnchor( elementId: string ) {
|
|
32
|
+
const element = getElementView( elementId );
|
|
33
|
+
|
|
34
|
+
return element?.closest( 'a' ) || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getDescendantAnchor( elementId: string ) {
|
|
38
|
+
const element = getElementView( elementId );
|
|
39
|
+
|
|
40
|
+
return element?.querySelector( 'a' ) || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getElementView( id: string ) {
|
|
44
|
+
const elementContainer = getContainer( id );
|
|
45
|
+
|
|
46
|
+
return elementContainer?.view?.el || null;
|
|
47
|
+
}
|