@elementor/editor-canvas 4.3.0-1050 → 4.3.0-1052
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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +57 -3
- package/dist/index.mjs +57 -3
- package/package.json +20 -20
- package/src/legacy/__tests__/create-templated-element-type.test.ts +50 -0
- package/src/legacy/create-nested-templated-element-type.ts +3 -1
- package/src/legacy/create-templated-element-type.ts +5 -2
- package/src/legacy/twig-rendering-utils.ts +11 -2
- package/src/renderers/__tests__/compute-html-tag.test.ts +55 -0
- package/src/renderers/__tests__/fixtures/html-tag-computer-cases.json +87 -0
- package/src/renderers/compute-html-tag.ts +73 -0
package/dist/index.d.mts
CHANGED
|
@@ -239,7 +239,7 @@ type CreateTemplatedElementTypeOptions = {
|
|
|
239
239
|
renderer: DomRenderer;
|
|
240
240
|
element: TemplatedElementConfig;
|
|
241
241
|
};
|
|
242
|
-
type TemplatedElementConfig = Required<Pick<V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary'
|
|
242
|
+
type TemplatedElementConfig = Required<Pick<V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary'>> & Pick<V1ElementConfig, 'default_html_tag' | 'html_tag_follows_link'>;
|
|
243
243
|
declare function createTemplatedElementView({ type, renderer, element, }: CreateTemplatedElementTypeOptions): typeof TemplatedElementView;
|
|
244
244
|
|
|
245
245
|
type NestedTemplatedElementConfig = TemplatedElementConfig & {
|
package/dist/index.d.ts
CHANGED
|
@@ -239,7 +239,7 @@ type CreateTemplatedElementTypeOptions = {
|
|
|
239
239
|
renderer: DomRenderer;
|
|
240
240
|
element: TemplatedElementConfig;
|
|
241
241
|
};
|
|
242
|
-
type TemplatedElementConfig = Required<Pick<V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary'
|
|
242
|
+
type TemplatedElementConfig = Required<Pick<V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary'>> & Pick<V1ElementConfig, 'default_html_tag' | 'html_tag_follows_link'>;
|
|
243
243
|
declare function createTemplatedElementView({ type, renderer, element, }: CreateTemplatedElementTypeOptions): typeof TemplatedElementView;
|
|
244
244
|
|
|
245
245
|
type NestedTemplatedElementConfig = TemplatedElementConfig & {
|
package/dist/index.js
CHANGED
|
@@ -2713,6 +2713,52 @@ function createElementViewClassDeclaration() {
|
|
|
2713
2713
|
// src/legacy/create-nested-templated-element-type.ts
|
|
2714
2714
|
var import_editor_elements6 = require("@elementor/editor-elements");
|
|
2715
2715
|
|
|
2716
|
+
// src/renderers/compute-html-tag.ts
|
|
2717
|
+
var DEFAULT_LINK_TAG = "a";
|
|
2718
|
+
function computeHtmlTag(settings, defaultTag, options = {}) {
|
|
2719
|
+
const followLink = options.followLink ?? true;
|
|
2720
|
+
if (followLink && settingsHaveActiveLink(settings)) {
|
|
2721
|
+
const link = settings.link;
|
|
2722
|
+
return extractLinkHtmlTag(isRecord(link) ? link : {});
|
|
2723
|
+
}
|
|
2724
|
+
const settingsTag = extractHtmlTagValue(settings.tag);
|
|
2725
|
+
if (null !== settingsTag && "" !== settingsTag) {
|
|
2726
|
+
return settingsTag;
|
|
2727
|
+
}
|
|
2728
|
+
return defaultTag;
|
|
2729
|
+
}
|
|
2730
|
+
function settingsHaveActiveLink(settings) {
|
|
2731
|
+
const link = settings.link;
|
|
2732
|
+
if (!isRecord(link)) {
|
|
2733
|
+
return false;
|
|
2734
|
+
}
|
|
2735
|
+
const href = extractHtmlTagValue(link.href);
|
|
2736
|
+
if (null !== href && "" !== href) {
|
|
2737
|
+
return true;
|
|
2738
|
+
}
|
|
2739
|
+
const attributes = link.attributes;
|
|
2740
|
+
return typeof attributes === "string" && "" !== attributes;
|
|
2741
|
+
}
|
|
2742
|
+
function extractLinkHtmlTag(link) {
|
|
2743
|
+
const tag = extractHtmlTagValue(link.tag);
|
|
2744
|
+
if (null !== tag && "" !== tag) {
|
|
2745
|
+
return tag;
|
|
2746
|
+
}
|
|
2747
|
+
return DEFAULT_LINK_TAG;
|
|
2748
|
+
}
|
|
2749
|
+
function extractHtmlTagValue(value) {
|
|
2750
|
+
if (isRecord(value) && typeof value.value === "string") {
|
|
2751
|
+
return value.value;
|
|
2752
|
+
}
|
|
2753
|
+
if (typeof value === "string") {
|
|
2754
|
+
return value;
|
|
2755
|
+
}
|
|
2756
|
+
return null;
|
|
2757
|
+
}
|
|
2758
|
+
function isRecord(value) {
|
|
2759
|
+
return typeof value === "object" && null !== value && !Array.isArray(value);
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2716
2762
|
// src/legacy/create-pending-element.ts
|
|
2717
2763
|
var import_editor_elements5 = require("@elementor/editor-elements");
|
|
2718
2764
|
function createPendingElement(wrapperView, data, options = {}) {
|
|
@@ -2773,7 +2819,13 @@ function setupTwigRenderer({ renderer, element }) {
|
|
|
2773
2819
|
transformers: settingsTransformersRegistry,
|
|
2774
2820
|
schema: element.atomic_props_schema
|
|
2775
2821
|
});
|
|
2776
|
-
return {
|
|
2822
|
+
return {
|
|
2823
|
+
templateKey,
|
|
2824
|
+
baseStylesDictionary,
|
|
2825
|
+
resolveProps,
|
|
2826
|
+
defaultHtmlTag: element.default_html_tag ?? "div",
|
|
2827
|
+
htmlTagFollowsLink: element.html_tag_follows_link ?? true
|
|
2828
|
+
};
|
|
2777
2829
|
}
|
|
2778
2830
|
function createBeforeRender(view) {
|
|
2779
2831
|
view._ensureViewIsIntact();
|
|
@@ -2813,7 +2865,7 @@ function createTemplatedElementView({
|
|
|
2813
2865
|
element
|
|
2814
2866
|
}) {
|
|
2815
2867
|
const BaseView = createElementViewClassDeclaration();
|
|
2816
|
-
const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer({
|
|
2868
|
+
const { templateKey, baseStylesDictionary, resolveProps, defaultHtmlTag, htmlTagFollowsLink } = setupTwigRenderer({
|
|
2817
2869
|
type,
|
|
2818
2870
|
renderer,
|
|
2819
2871
|
element
|
|
@@ -2883,6 +2935,7 @@ function createTemplatedElementView({
|
|
|
2883
2935
|
interaction_id: this.getInteractionId(),
|
|
2884
2936
|
type,
|
|
2885
2937
|
settings,
|
|
2938
|
+
tag: computeHtmlTag(settings, defaultHtmlTag, { followLink: htmlTagFollowsLink }),
|
|
2886
2939
|
base_styles: baseStylesDictionary,
|
|
2887
2940
|
...this.getResolverRenderContext?.() ?? {}
|
|
2888
2941
|
};
|
|
@@ -2972,7 +3025,7 @@ function createNestedTemplatedElementView({
|
|
|
2972
3025
|
element
|
|
2973
3026
|
}) {
|
|
2974
3027
|
const legacyWindow = window;
|
|
2975
|
-
const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer({
|
|
3028
|
+
const { templateKey, baseStylesDictionary, resolveProps, defaultHtmlTag, htmlTagFollowsLink } = setupTwigRenderer({
|
|
2976
3029
|
type,
|
|
2977
3030
|
renderer,
|
|
2978
3031
|
element
|
|
@@ -3047,6 +3100,7 @@ function createNestedTemplatedElementView({
|
|
|
3047
3100
|
interaction_id: this.getInteractionId(),
|
|
3048
3101
|
type,
|
|
3049
3102
|
settings: resolvedSettings,
|
|
3103
|
+
tag: computeHtmlTag(resolvedSettings, defaultHtmlTag, { followLink: htmlTagFollowsLink }),
|
|
3050
3104
|
base_styles: baseStylesDictionary,
|
|
3051
3105
|
editor_attributes: buildEditorAttributes(model),
|
|
3052
3106
|
editor_classes: buildEditorClasses(model),
|
package/dist/index.mjs
CHANGED
|
@@ -2665,6 +2665,52 @@ function createElementViewClassDeclaration() {
|
|
|
2665
2665
|
// src/legacy/create-nested-templated-element-type.ts
|
|
2666
2666
|
import { ELEMENT_STYLE_CHANGE_EVENT as ELEMENT_STYLE_CHANGE_EVENT2 } from "@elementor/editor-elements";
|
|
2667
2667
|
|
|
2668
|
+
// src/renderers/compute-html-tag.ts
|
|
2669
|
+
var DEFAULT_LINK_TAG = "a";
|
|
2670
|
+
function computeHtmlTag(settings, defaultTag, options = {}) {
|
|
2671
|
+
const followLink = options.followLink ?? true;
|
|
2672
|
+
if (followLink && settingsHaveActiveLink(settings)) {
|
|
2673
|
+
const link = settings.link;
|
|
2674
|
+
return extractLinkHtmlTag(isRecord(link) ? link : {});
|
|
2675
|
+
}
|
|
2676
|
+
const settingsTag = extractHtmlTagValue(settings.tag);
|
|
2677
|
+
if (null !== settingsTag && "" !== settingsTag) {
|
|
2678
|
+
return settingsTag;
|
|
2679
|
+
}
|
|
2680
|
+
return defaultTag;
|
|
2681
|
+
}
|
|
2682
|
+
function settingsHaveActiveLink(settings) {
|
|
2683
|
+
const link = settings.link;
|
|
2684
|
+
if (!isRecord(link)) {
|
|
2685
|
+
return false;
|
|
2686
|
+
}
|
|
2687
|
+
const href = extractHtmlTagValue(link.href);
|
|
2688
|
+
if (null !== href && "" !== href) {
|
|
2689
|
+
return true;
|
|
2690
|
+
}
|
|
2691
|
+
const attributes = link.attributes;
|
|
2692
|
+
return typeof attributes === "string" && "" !== attributes;
|
|
2693
|
+
}
|
|
2694
|
+
function extractLinkHtmlTag(link) {
|
|
2695
|
+
const tag = extractHtmlTagValue(link.tag);
|
|
2696
|
+
if (null !== tag && "" !== tag) {
|
|
2697
|
+
return tag;
|
|
2698
|
+
}
|
|
2699
|
+
return DEFAULT_LINK_TAG;
|
|
2700
|
+
}
|
|
2701
|
+
function extractHtmlTagValue(value) {
|
|
2702
|
+
if (isRecord(value) && typeof value.value === "string") {
|
|
2703
|
+
return value.value;
|
|
2704
|
+
}
|
|
2705
|
+
if (typeof value === "string") {
|
|
2706
|
+
return value;
|
|
2707
|
+
}
|
|
2708
|
+
return null;
|
|
2709
|
+
}
|
|
2710
|
+
function isRecord(value) {
|
|
2711
|
+
return typeof value === "object" && null !== value && !Array.isArray(value);
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2668
2714
|
// src/legacy/create-pending-element.ts
|
|
2669
2715
|
import {
|
|
2670
2716
|
addModelToParent,
|
|
@@ -2730,7 +2776,13 @@ function setupTwigRenderer({ renderer, element }) {
|
|
|
2730
2776
|
transformers: settingsTransformersRegistry,
|
|
2731
2777
|
schema: element.atomic_props_schema
|
|
2732
2778
|
});
|
|
2733
|
-
return {
|
|
2779
|
+
return {
|
|
2780
|
+
templateKey,
|
|
2781
|
+
baseStylesDictionary,
|
|
2782
|
+
resolveProps,
|
|
2783
|
+
defaultHtmlTag: element.default_html_tag ?? "div",
|
|
2784
|
+
htmlTagFollowsLink: element.html_tag_follows_link ?? true
|
|
2785
|
+
};
|
|
2734
2786
|
}
|
|
2735
2787
|
function createBeforeRender(view) {
|
|
2736
2788
|
view._ensureViewIsIntact();
|
|
@@ -2770,7 +2822,7 @@ function createTemplatedElementView({
|
|
|
2770
2822
|
element
|
|
2771
2823
|
}) {
|
|
2772
2824
|
const BaseView = createElementViewClassDeclaration();
|
|
2773
|
-
const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer({
|
|
2825
|
+
const { templateKey, baseStylesDictionary, resolveProps, defaultHtmlTag, htmlTagFollowsLink } = setupTwigRenderer({
|
|
2774
2826
|
type,
|
|
2775
2827
|
renderer,
|
|
2776
2828
|
element
|
|
@@ -2840,6 +2892,7 @@ function createTemplatedElementView({
|
|
|
2840
2892
|
interaction_id: this.getInteractionId(),
|
|
2841
2893
|
type,
|
|
2842
2894
|
settings,
|
|
2895
|
+
tag: computeHtmlTag(settings, defaultHtmlTag, { followLink: htmlTagFollowsLink }),
|
|
2843
2896
|
base_styles: baseStylesDictionary,
|
|
2844
2897
|
...this.getResolverRenderContext?.() ?? {}
|
|
2845
2898
|
};
|
|
@@ -2929,7 +2982,7 @@ function createNestedTemplatedElementView({
|
|
|
2929
2982
|
element
|
|
2930
2983
|
}) {
|
|
2931
2984
|
const legacyWindow = window;
|
|
2932
|
-
const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer({
|
|
2985
|
+
const { templateKey, baseStylesDictionary, resolveProps, defaultHtmlTag, htmlTagFollowsLink } = setupTwigRenderer({
|
|
2933
2986
|
type,
|
|
2934
2987
|
renderer,
|
|
2935
2988
|
element
|
|
@@ -3004,6 +3057,7 @@ function createNestedTemplatedElementView({
|
|
|
3004
3057
|
interaction_id: this.getInteractionId(),
|
|
3005
3058
|
type,
|
|
3006
3059
|
settings: resolvedSettings,
|
|
3060
|
+
tag: computeHtmlTag(resolvedSettings, defaultHtmlTag, { followLink: htmlTagFollowsLink }),
|
|
3007
3061
|
base_styles: baseStylesDictionary,
|
|
3008
3062
|
editor_attributes: buildEditorAttributes(model),
|
|
3009
3063
|
editor_classes: buildEditorClasses(model),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-canvas",
|
|
3
3
|
"description": "Elementor Editor Canvas",
|
|
4
|
-
"version": "4.3.0-
|
|
4
|
+
"version": "4.3.0-1052",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -37,26 +37,26 @@
|
|
|
37
37
|
"react-dom": "^18.3.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@elementor/editor": "4.3.0-
|
|
41
|
-
"@elementor/editor-controls": "4.3.0-
|
|
42
|
-
"@elementor/editor-documents": "4.3.0-
|
|
43
|
-
"@elementor/editor-elements": "4.3.0-
|
|
44
|
-
"@elementor/editor-interactions": "4.3.0-
|
|
45
|
-
"@elementor/editor-mcp": "4.3.0-
|
|
46
|
-
"@elementor/editor-notifications": "4.3.0-
|
|
47
|
-
"@elementor/editor-props": "4.3.0-
|
|
48
|
-
"@elementor/editor-responsive": "4.3.0-
|
|
49
|
-
"@elementor/editor-styles": "4.3.0-
|
|
50
|
-
"@elementor/editor-styles-repository": "4.3.0-
|
|
51
|
-
"@elementor/editor-ui": "4.3.0-
|
|
52
|
-
"@elementor/editor-v1-adapters": "4.3.0-
|
|
53
|
-
"@elementor/events": "4.3.0-
|
|
54
|
-
"@elementor/http-client": "4.3.0-
|
|
55
|
-
"@elementor/schema": "4.3.0-
|
|
56
|
-
"@elementor/twing": "4.3.0-
|
|
40
|
+
"@elementor/editor": "4.3.0-1052",
|
|
41
|
+
"@elementor/editor-controls": "4.3.0-1052",
|
|
42
|
+
"@elementor/editor-documents": "4.3.0-1052",
|
|
43
|
+
"@elementor/editor-elements": "4.3.0-1052",
|
|
44
|
+
"@elementor/editor-interactions": "4.3.0-1052",
|
|
45
|
+
"@elementor/editor-mcp": "4.3.0-1052",
|
|
46
|
+
"@elementor/editor-notifications": "4.3.0-1052",
|
|
47
|
+
"@elementor/editor-props": "4.3.0-1052",
|
|
48
|
+
"@elementor/editor-responsive": "4.3.0-1052",
|
|
49
|
+
"@elementor/editor-styles": "4.3.0-1052",
|
|
50
|
+
"@elementor/editor-styles-repository": "4.3.0-1052",
|
|
51
|
+
"@elementor/editor-ui": "4.3.0-1052",
|
|
52
|
+
"@elementor/editor-v1-adapters": "4.3.0-1052",
|
|
53
|
+
"@elementor/events": "4.3.0-1052",
|
|
54
|
+
"@elementor/http-client": "4.3.0-1052",
|
|
55
|
+
"@elementor/schema": "4.3.0-1052",
|
|
56
|
+
"@elementor/twing": "4.3.0-1052",
|
|
57
57
|
"@elementor/ui": "1.37.5",
|
|
58
|
-
"@elementor/utils": "4.3.0-
|
|
59
|
-
"@elementor/wp-media": "4.3.0-
|
|
58
|
+
"@elementor/utils": "4.3.0-1052",
|
|
59
|
+
"@elementor/wp-media": "4.3.0-1052",
|
|
60
60
|
"@floating-ui/react": "^0.27.5",
|
|
61
61
|
"@wordpress/i18n": "^5.13.0",
|
|
62
62
|
"dompurify": "^3.2.6"
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createTemplatedElementType,
|
|
6
6
|
createTemplatedElementView,
|
|
7
7
|
} from '../create-templated-element-type';
|
|
8
|
+
import { type ElementView } from '../types';
|
|
8
9
|
|
|
9
10
|
const MOCK_ELEMENT_TYPE = 'test-element';
|
|
10
11
|
const MOCK_HTML = '<div>Element</div>';
|
|
@@ -19,6 +20,8 @@ const createMockElementConfig = () => ( {
|
|
|
19
20
|
twig_main_template: 'main',
|
|
20
21
|
atomic_props_schema: {},
|
|
21
22
|
base_styles_dictionary: {},
|
|
23
|
+
default_html_tag: 'div',
|
|
24
|
+
html_tag_follows_link: true,
|
|
22
25
|
} );
|
|
23
26
|
|
|
24
27
|
describe( 'createTemplatedElementType', () => {
|
|
@@ -116,6 +119,53 @@ describe( 'createTemplatedElementView', () => {
|
|
|
116
119
|
expect( utils.register ).toHaveBeenCalledWith( 'template2', '<div>Template 2</div>' );
|
|
117
120
|
} );
|
|
118
121
|
} );
|
|
122
|
+
|
|
123
|
+
describe( 'render context', () => {
|
|
124
|
+
it( 'should inject computed tag into the twig render context', async () => {
|
|
125
|
+
// Arrange
|
|
126
|
+
const utils = createMockRenderer();
|
|
127
|
+
const ViewClass = createTemplatedElementView( {
|
|
128
|
+
type: MOCK_ELEMENT_TYPE,
|
|
129
|
+
renderer: utils,
|
|
130
|
+
element: {
|
|
131
|
+
...createMockElementConfig(),
|
|
132
|
+
twig_templates: {
|
|
133
|
+
main: '<{{ tag | e("html_tag") }}></{{ tag | e("html_tag") }}>',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
} );
|
|
137
|
+
|
|
138
|
+
const view = new ViewClass() as unknown as ElementView & {
|
|
139
|
+
_abortController: AbortController;
|
|
140
|
+
};
|
|
141
|
+
view.model = {
|
|
142
|
+
get: ( key: string ) => {
|
|
143
|
+
if ( key === 'id' ) {
|
|
144
|
+
return 'test-id';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if ( key === 'settings' ) {
|
|
148
|
+
return { toJSON: () => ( {} ) };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return undefined;
|
|
152
|
+
},
|
|
153
|
+
} as unknown as ElementView[ 'model' ];
|
|
154
|
+
view.isRendered = false;
|
|
155
|
+
view._abortController = new AbortController();
|
|
156
|
+
view.triggerMethod = jest.fn();
|
|
157
|
+
view.bindUIElements = jest.fn();
|
|
158
|
+
view.$el = { html: jest.fn() } as unknown as ElementView[ '$el' ];
|
|
159
|
+
view._ensureViewIsIntact = jest.fn();
|
|
160
|
+
view.resetChildViewContainer = jest.fn();
|
|
161
|
+
|
|
162
|
+
// Act
|
|
163
|
+
await view._renderTemplate();
|
|
164
|
+
|
|
165
|
+
// Assert
|
|
166
|
+
expect( utils.render ).toHaveBeenCalledWith( 'main', expect.objectContaining( { tag: 'div' } ) );
|
|
167
|
+
} );
|
|
168
|
+
} );
|
|
119
169
|
} );
|
|
120
170
|
|
|
121
171
|
describe( 'canBeTemplated', () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ELEMENT_STYLE_CHANGE_EVENT, type V1ElementModelProps } from '@elementor/editor-elements';
|
|
2
2
|
|
|
3
|
+
import { computeHtmlTag } from '../renderers/compute-html-tag';
|
|
3
4
|
import { type DomRenderer } from '../renderers/create-dom-renderer';
|
|
4
5
|
import { signalizedProcess } from '../utils/signalized-process';
|
|
5
6
|
import { createPendingElement } from './create-pending-element';
|
|
@@ -93,7 +94,7 @@ export function createNestedTemplatedElementView( {
|
|
|
93
94
|
}: CreateNestedTemplatedElementViewOptions ): NestedTemplatedElementViewClass {
|
|
94
95
|
const legacyWindow = window as unknown as LegacyWindow;
|
|
95
96
|
|
|
96
|
-
const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer( {
|
|
97
|
+
const { templateKey, baseStylesDictionary, resolveProps, defaultHtmlTag, htmlTagFollowsLink } = setupTwigRenderer( {
|
|
97
98
|
type,
|
|
98
99
|
renderer,
|
|
99
100
|
element,
|
|
@@ -198,6 +199,7 @@ export function createNestedTemplatedElementView( {
|
|
|
198
199
|
interaction_id: this.getInteractionId(),
|
|
199
200
|
type,
|
|
200
201
|
settings: resolvedSettings,
|
|
202
|
+
tag: computeHtmlTag( resolvedSettings, defaultHtmlTag, { followLink: htmlTagFollowsLink } ),
|
|
201
203
|
base_styles: baseStylesDictionary,
|
|
202
204
|
editor_attributes: buildEditorAttributes( model ),
|
|
203
205
|
editor_classes: buildEditorClasses( model ),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
2
|
|
|
3
|
+
import { computeHtmlTag } from '../renderers/compute-html-tag';
|
|
3
4
|
import { type DomRenderer } from '../renderers/create-dom-renderer';
|
|
4
5
|
import { signalizedProcess } from '../utils/signalized-process';
|
|
5
6
|
import { createElementViewClassDeclaration } from './create-element-type';
|
|
@@ -27,7 +28,8 @@ export type CreateTemplatedElementTypeOptions = {
|
|
|
27
28
|
|
|
28
29
|
export type TemplatedElementConfig = Required<
|
|
29
30
|
Pick< V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary' >
|
|
30
|
-
|
|
31
|
+
> &
|
|
32
|
+
Pick< V1ElementConfig, 'default_html_tag' | 'html_tag_follows_link' >;
|
|
31
33
|
|
|
32
34
|
export function createTemplatedElementType( {
|
|
33
35
|
type,
|
|
@@ -69,7 +71,7 @@ export function createTemplatedElementView( {
|
|
|
69
71
|
}: CreateTemplatedElementTypeOptions ): typeof TemplatedElementView {
|
|
70
72
|
const BaseView = createElementViewClassDeclaration();
|
|
71
73
|
|
|
72
|
-
const { templateKey, baseStylesDictionary, resolveProps } = setupTwigRenderer( {
|
|
74
|
+
const { templateKey, baseStylesDictionary, resolveProps, defaultHtmlTag, htmlTagFollowsLink } = setupTwigRenderer( {
|
|
73
75
|
type,
|
|
74
76
|
renderer,
|
|
75
77
|
element,
|
|
@@ -165,6 +167,7 @@ export function createTemplatedElementView( {
|
|
|
165
167
|
interaction_id: this.getInteractionId(),
|
|
166
168
|
type,
|
|
167
169
|
settings,
|
|
170
|
+
tag: computeHtmlTag( settings, defaultHtmlTag, { followLink: htmlTagFollowsLink } ),
|
|
168
171
|
base_styles: baseStylesDictionary,
|
|
169
172
|
...( this.getResolverRenderContext?.() ?? {} ),
|
|
170
173
|
};
|
|
@@ -7,7 +7,8 @@ import { type ElementView, type RenderContext } from './types';
|
|
|
7
7
|
|
|
8
8
|
export type TwigElementConfig = Required<
|
|
9
9
|
Pick< V1ElementConfig, 'twig_templates' | 'twig_main_template' | 'atomic_props_schema' | 'base_styles_dictionary' >
|
|
10
|
-
|
|
10
|
+
> &
|
|
11
|
+
Pick< V1ElementConfig, 'default_html_tag' | 'html_tag_follows_link' >;
|
|
11
12
|
|
|
12
13
|
export type SetupTwigRendererOptions = {
|
|
13
14
|
type: string;
|
|
@@ -19,6 +20,8 @@ export type SetupTwigRendererResult = {
|
|
|
19
20
|
templateKey: string;
|
|
20
21
|
baseStylesDictionary: Record< string, unknown >;
|
|
21
22
|
resolveProps: PropsResolver;
|
|
23
|
+
defaultHtmlTag: string;
|
|
24
|
+
htmlTagFollowsLink: boolean;
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
export function setupTwigRenderer( { renderer, element }: SetupTwigRendererOptions ): SetupTwigRendererResult {
|
|
@@ -34,7 +37,13 @@ export function setupTwigRenderer( { renderer, element }: SetupTwigRendererOptio
|
|
|
34
37
|
schema: element.atomic_props_schema,
|
|
35
38
|
} );
|
|
36
39
|
|
|
37
|
-
return {
|
|
40
|
+
return {
|
|
41
|
+
templateKey,
|
|
42
|
+
baseStylesDictionary,
|
|
43
|
+
resolveProps,
|
|
44
|
+
defaultHtmlTag: element.default_html_tag ?? 'div',
|
|
45
|
+
htmlTagFollowsLink: element.html_tag_follows_link ?? true,
|
|
46
|
+
};
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
export interface TwigViewInterface extends Omit< ElementView, 'getResolverRenderContext' > {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
import { computeHtmlTag } from '../compute-html-tag';
|
|
5
|
+
|
|
6
|
+
type HtmlTagComputerCase = {
|
|
7
|
+
settings: Record< string, unknown >;
|
|
8
|
+
default: string;
|
|
9
|
+
followLink: boolean;
|
|
10
|
+
expected: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const contractCases: HtmlTagComputerCase[] = JSON.parse(
|
|
14
|
+
readFileSync( join( __dirname, 'fixtures/html-tag-computer-cases.json' ), 'utf8' )
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
describe( 'computeHtmlTag', () => {
|
|
18
|
+
it.each( contractCases )(
|
|
19
|
+
'contract case: default=$default followLink=$followLink expected=$expected',
|
|
20
|
+
( { settings, default: defaultTag, followLink, expected } ) => {
|
|
21
|
+
expect( computeHtmlTag( settings, defaultTag, { followLink } ) ).toBe( expected );
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
it( 'uses link tag when href is present', () => {
|
|
26
|
+
expect(
|
|
27
|
+
computeHtmlTag(
|
|
28
|
+
{
|
|
29
|
+
tag: 'div',
|
|
30
|
+
link: {
|
|
31
|
+
href: 'https://example.com',
|
|
32
|
+
tag: 'a',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
'div'
|
|
36
|
+
)
|
|
37
|
+
).toBe( 'a' );
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
it( 'ignores link when followLink is false', () => {
|
|
41
|
+
expect(
|
|
42
|
+
computeHtmlTag(
|
|
43
|
+
{
|
|
44
|
+
tag: 'h4',
|
|
45
|
+
link: {
|
|
46
|
+
href: 'https://example.com',
|
|
47
|
+
tag: 'a',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
'h2',
|
|
51
|
+
{ followLink: false }
|
|
52
|
+
)
|
|
53
|
+
).toBe( 'h4' );
|
|
54
|
+
} );
|
|
55
|
+
} );
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"settings": {},
|
|
4
|
+
"default": "div",
|
|
5
|
+
"followLink": true,
|
|
6
|
+
"expected": "div"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"settings": { "tag": "header" },
|
|
10
|
+
"default": "div",
|
|
11
|
+
"followLink": true,
|
|
12
|
+
"expected": "header"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"settings": { "tag": "" },
|
|
16
|
+
"default": "div",
|
|
17
|
+
"followLink": true,
|
|
18
|
+
"expected": "div"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"settings": {
|
|
22
|
+
"link": {
|
|
23
|
+
"href": "https://example.com",
|
|
24
|
+
"tag": "a"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"default": "div",
|
|
28
|
+
"followLink": true,
|
|
29
|
+
"expected": "a"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"settings": {
|
|
33
|
+
"link": {
|
|
34
|
+
"tag": "a"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"default": "button",
|
|
38
|
+
"followLink": true,
|
|
39
|
+
"expected": "button"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"settings": {
|
|
43
|
+
"link": {
|
|
44
|
+
"tag": "a",
|
|
45
|
+
"attributes": "href=\"https://example.com\""
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"default": "div",
|
|
49
|
+
"followLink": true,
|
|
50
|
+
"expected": "a"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"settings": {
|
|
54
|
+
"tag": "div",
|
|
55
|
+
"link": {
|
|
56
|
+
"href": "https://example.com",
|
|
57
|
+
"tag": "a"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"default": "div",
|
|
61
|
+
"followLink": true,
|
|
62
|
+
"expected": "a"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"settings": {
|
|
66
|
+
"tag": "div",
|
|
67
|
+
"link": {
|
|
68
|
+
"href": "https://example.com",
|
|
69
|
+
"tag": "a"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"default": "div",
|
|
73
|
+
"followLink": false,
|
|
74
|
+
"expected": "div"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"settings": {
|
|
78
|
+
"tag": {
|
|
79
|
+
"$$type": "string",
|
|
80
|
+
"value": "h3"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"default": "h2",
|
|
84
|
+
"followLink": true,
|
|
85
|
+
"expected": "h3"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export type ComputeHtmlTagOptions = {
|
|
2
|
+
followLink?: boolean;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_LINK_TAG = 'a';
|
|
6
|
+
|
|
7
|
+
type LinkSettings = Record< string, unknown >;
|
|
8
|
+
|
|
9
|
+
export function computeHtmlTag(
|
|
10
|
+
settings: Record< string, unknown >,
|
|
11
|
+
defaultTag: string,
|
|
12
|
+
options: ComputeHtmlTagOptions = {}
|
|
13
|
+
): string {
|
|
14
|
+
const followLink = options.followLink ?? true;
|
|
15
|
+
|
|
16
|
+
if ( followLink && settingsHaveActiveLink( settings ) ) {
|
|
17
|
+
const link = settings.link;
|
|
18
|
+
|
|
19
|
+
return extractLinkHtmlTag( isRecord( link ) ? link : {} );
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const settingsTag = extractHtmlTagValue( settings.tag );
|
|
23
|
+
|
|
24
|
+
if ( null !== settingsTag && '' !== settingsTag ) {
|
|
25
|
+
return settingsTag;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return defaultTag;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function settingsHaveActiveLink( settings: Record< string, unknown > ): boolean {
|
|
32
|
+
const link = settings.link;
|
|
33
|
+
|
|
34
|
+
if ( ! isRecord( link ) ) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const href = extractHtmlTagValue( link.href );
|
|
39
|
+
|
|
40
|
+
if ( null !== href && '' !== href ) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const attributes = link.attributes;
|
|
45
|
+
|
|
46
|
+
return typeof attributes === 'string' && '' !== attributes;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extractLinkHtmlTag( link: LinkSettings ): string {
|
|
50
|
+
const tag = extractHtmlTagValue( link.tag );
|
|
51
|
+
|
|
52
|
+
if ( null !== tag && '' !== tag ) {
|
|
53
|
+
return tag;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return DEFAULT_LINK_TAG;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function extractHtmlTagValue( value: unknown ): string | null {
|
|
60
|
+
if ( isRecord( value ) && typeof value.value === 'string' ) {
|
|
61
|
+
return value.value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ( typeof value === 'string' ) {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isRecord( value: unknown ): value is Record< string, unknown > {
|
|
72
|
+
return typeof value === 'object' && null !== value && ! Array.isArray( value );
|
|
73
|
+
}
|