@elementor/editor-canvas 0.13.1 → 0.15.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +33 -0
- package/dist/index.d.mts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +184 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +192 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -8
- package/src/__tests__/__mocks__/styles-schema.ts +3 -3
- package/src/__tests__/settings-props-resolver.test.ts +1 -1
- package/src/__tests__/styles-prop-resolver.test.ts +7 -7
- package/src/components/__tests__/elements-overlays.test.tsx +40 -35
- package/src/components/element-overlay.tsx +3 -2
- package/src/components/elements-overlays.tsx +26 -9
- package/src/hooks/use-floating-on-element.ts +9 -6
- package/src/init-settings-transformers.ts +1 -5
- package/src/init-style-transformers.ts +2 -6
- package/src/init-styles-renderer.ts +1 -1
- package/src/legacy/__tests__/signalized-process.test.ts +80 -0
- package/src/legacy/create-element-type.ts +2 -2
- package/src/legacy/create-templated-element-type.ts +131 -0
- package/src/legacy/init-legacy-views.ts +7 -1
- package/src/legacy/signalized-process.ts +35 -0
- package/src/legacy/types.ts +27 -3
- package/src/renderers/__tests__/create-dom-renderer.test.ts +66 -0
- package/src/renderers/__tests__/create-props-resolver.test.ts +123 -15
- package/src/renderers/create-dom-renderer.ts +56 -0
- package/src/renderers/create-props-resolver.ts +10 -8
- package/src/renderers/render-styles.ts +4 -0
- package/src/transformers/create-transformers-registry.ts +16 -5
- package/src/transformers/styles/background-image-position-offset-transformer.ts +1 -1
- package/src/transformers/styles/background-image-size-scale-transformer.ts +1 -1
- package/src/transformers/types.ts +1 -5
package/dist/index.mjs
CHANGED
|
@@ -3,8 +3,13 @@ import { injectIntoTop } from "@elementor/editor";
|
|
|
3
3
|
|
|
4
4
|
// src/components/elements-overlays.tsx
|
|
5
5
|
import * as React2 from "react";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { getElements, useSelectedElement } from "@elementor/editor-elements";
|
|
7
|
+
import {
|
|
8
|
+
__privateUseIsRouteActive as useIsRouteActive,
|
|
9
|
+
__privateUseListenTo as useListenTo,
|
|
10
|
+
useEditMode,
|
|
11
|
+
windowEvent
|
|
12
|
+
} from "@elementor/editor-v1-adapters";
|
|
8
13
|
|
|
9
14
|
// src/components/element-overlay.tsx
|
|
10
15
|
import * as React from "react";
|
|
@@ -46,7 +51,7 @@ function groupProps(props) {
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
// src/hooks/use-floating-on-element.ts
|
|
49
|
-
import { useState } from "react";
|
|
54
|
+
import { useEffect as useEffect2, useState } from "react";
|
|
50
55
|
import { autoUpdate, offset, size, useFloating } from "@floating-ui/react";
|
|
51
56
|
function useFloatingOnElement({ element, isSelected }) {
|
|
52
57
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -54,10 +59,7 @@ function useFloatingOnElement({ element, isSelected }) {
|
|
|
54
59
|
// Must be controlled for interactions (like hover) to work.
|
|
55
60
|
open: isOpen || isSelected,
|
|
56
61
|
onOpenChange: setIsOpen,
|
|
57
|
-
|
|
58
|
-
whileElementsMounted: (...args) => autoUpdate(...args, { animationFrame: true }),
|
|
59
|
-
// The first element in the canvas is `display: contents` so we need to use the first child.
|
|
60
|
-
elements: { reference: element.firstElementChild },
|
|
62
|
+
whileElementsMounted: autoUpdate,
|
|
61
63
|
middleware: [
|
|
62
64
|
// Match the floating element's size to the reference element.
|
|
63
65
|
size({
|
|
@@ -72,6 +74,9 @@ function useFloatingOnElement({ element, isSelected }) {
|
|
|
72
74
|
offset(({ rects }) => -rects.reference.height / 2 - rects.floating.height / 2)
|
|
73
75
|
]
|
|
74
76
|
});
|
|
77
|
+
useEffect2(() => {
|
|
78
|
+
refs.setReference(element);
|
|
79
|
+
}, [element, refs]);
|
|
75
80
|
return {
|
|
76
81
|
isVisible: isOpen || isSelected,
|
|
77
82
|
context,
|
|
@@ -90,7 +95,7 @@ var OverlayBox = styled(Box, { shouldForwardProp: (prop) => prop !== "isSelected
|
|
|
90
95
|
outlineOffset: isSelected ? "-2px" : "-1px",
|
|
91
96
|
pointerEvents: "none"
|
|
92
97
|
}));
|
|
93
|
-
function ElementOverlay({ element, isSelected }) {
|
|
98
|
+
function ElementOverlay({ element, isSelected, id }) {
|
|
94
99
|
const { context, floating, isVisible } = useFloatingOnElement({ element, isSelected });
|
|
95
100
|
const { getFloatingProps, getReferenceProps } = useInteractions([useHover(context)]);
|
|
96
101
|
useBindReactPropsToElement(element, getReferenceProps);
|
|
@@ -100,7 +105,7 @@ function ElementOverlay({ element, isSelected }) {
|
|
|
100
105
|
ref: floating.setRef,
|
|
101
106
|
isSelected,
|
|
102
107
|
style: floating.styles,
|
|
103
|
-
"data-element-overlay":
|
|
108
|
+
"data-element-overlay": id,
|
|
104
109
|
role: "presentation",
|
|
105
110
|
...getFloatingProps()
|
|
106
111
|
}
|
|
@@ -110,31 +115,38 @@ function ElementOverlay({ element, isSelected }) {
|
|
|
110
115
|
// src/components/elements-overlays.tsx
|
|
111
116
|
function ElementsOverlays() {
|
|
112
117
|
const selected = useSelectedElement();
|
|
113
|
-
const
|
|
118
|
+
const elements = useElementsDom();
|
|
114
119
|
const currentEditMode = useEditMode();
|
|
115
120
|
const isEditMode = currentEditMode === "edit";
|
|
116
121
|
const isKitRouteActive = useIsRouteActive("panel/global");
|
|
117
122
|
const isActive = isEditMode && !isKitRouteActive;
|
|
118
|
-
return isActive &&
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
return isActive && elements.map(([id, element]) => /* @__PURE__ */ React2.createElement(ElementOverlay, { key: id, id, element, isSelected: selected.element?.id === id }));
|
|
124
|
+
}
|
|
125
|
+
var ELEMENTS_DATA_ATTR = "atomic";
|
|
126
|
+
function useElementsDom() {
|
|
127
|
+
return useListenTo(
|
|
128
|
+
[windowEvent("elementor/editor/element-rendered"), windowEvent("elementor/editor/element-destroyed")],
|
|
129
|
+
() => {
|
|
130
|
+
return getElements().filter((el) => ELEMENTS_DATA_ATTR in (el.view?.el?.dataset ?? {})).map((element) => [element.id, element.view?.getDomElement?.()?.get?.(0)]).filter((item) => !!item[1]);
|
|
124
131
|
}
|
|
125
|
-
)
|
|
132
|
+
);
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
// src/transformers/create-transformers-registry.ts
|
|
129
136
|
function createTransformersRegistry() {
|
|
130
137
|
const transformers = {};
|
|
138
|
+
let fallbackTransformer = null;
|
|
131
139
|
return {
|
|
132
|
-
register(
|
|
133
|
-
transformers[
|
|
140
|
+
register(type, transformer) {
|
|
141
|
+
transformers[type] = transformer;
|
|
142
|
+
return this;
|
|
143
|
+
},
|
|
144
|
+
registerFallback(transformer) {
|
|
145
|
+
fallbackTransformer = transformer;
|
|
134
146
|
return this;
|
|
135
147
|
},
|
|
136
|
-
|
|
137
|
-
return transformers;
|
|
148
|
+
get(type) {
|
|
149
|
+
return transformers[type] ?? fallbackTransformer;
|
|
138
150
|
}
|
|
139
151
|
};
|
|
140
152
|
}
|
|
@@ -200,7 +212,7 @@ var plainTransformer = createTransformer((value) => {
|
|
|
200
212
|
|
|
201
213
|
// src/init-settings-transformers.ts
|
|
202
214
|
function initSettingsTransformers() {
|
|
203
|
-
settingsTransformersRegistry.register("
|
|
215
|
+
settingsTransformersRegistry.register("classes", arrayTransformer).register("link", linkTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).registerFallback(plainTransformer);
|
|
204
216
|
}
|
|
205
217
|
|
|
206
218
|
// src/style-transformers-registry.ts
|
|
@@ -242,12 +254,12 @@ var backgroundImageOverlayTransformer = createTransformer((value) => {
|
|
|
242
254
|
|
|
243
255
|
// src/transformers/styles/background-image-position-offset-transformer.ts
|
|
244
256
|
var backgroundImagePositionOffsetTransformer = createTransformer(
|
|
245
|
-
({ x
|
|
257
|
+
({ x, y }) => `${x ?? "0px"} ${y ?? "0px"}`
|
|
246
258
|
);
|
|
247
259
|
|
|
248
260
|
// src/transformers/styles/background-image-size-scale-transformer.ts
|
|
249
261
|
var backgroundImageSizeScaleTransformer = createTransformer(
|
|
250
|
-
({ width
|
|
262
|
+
({ width, height }) => `${width ?? "auto"} ${height ?? "auto"}`
|
|
251
263
|
);
|
|
252
264
|
|
|
253
265
|
// src/transformers/styles/background-transformer.ts
|
|
@@ -317,7 +329,7 @@ function initStyleTransformers() {
|
|
|
317
329
|
["block-start", "block-end", "inline-start", "inline-end"],
|
|
318
330
|
({ propKey, key }) => `${propKey}-${key}`
|
|
319
331
|
)
|
|
320
|
-
).register("
|
|
332
|
+
).register("box-shadow", createCombineArrayTransformer(",")).register("background", backgroundTransformer).register("background-overlay", createCombineArrayTransformer(",")).register("background-color-overlay", backgroundColorOverlayTransformer).register("background-image-overlay", backgroundImageOverlayTransformer).register("background-gradient-overlay", backgroundGradientOverlayTransformer).register("gradient-color-stop", createCombineArrayTransformer(",")).register("color-stop", colorStopTransformer).register("background-image-position-offset", backgroundImagePositionOffsetTransformer).register("background-image-size-scale", backgroundImageSizeScaleTransformer).register("image-src", imageSrcTransformer).register("image", imageTransformer).register(
|
|
321
333
|
"layout-direction",
|
|
322
334
|
createMultiPropsTransformer(["row", "column"], ({ propKey, key }) => `${key}-${propKey}`)
|
|
323
335
|
).register(
|
|
@@ -332,7 +344,7 @@ function initStyleTransformers() {
|
|
|
332
344
|
["start-start", "start-end", "end-start", "end-end"],
|
|
333
345
|
({ key }) => `border-${key}-radius`
|
|
334
346
|
)
|
|
335
|
-
);
|
|
347
|
+
).registerFallback(plainTransformer);
|
|
336
348
|
}
|
|
337
349
|
|
|
338
350
|
// src/init-styles-renderer.ts
|
|
@@ -353,9 +365,6 @@ function createPropsResolver({ transformers, schema: initialSchema, onPropResolv
|
|
|
353
365
|
Object.entries(schema).map(async ([key, type]) => {
|
|
354
366
|
const value = props[key] ?? type.default;
|
|
355
367
|
const transformed = await transform({ value, key, type, signal });
|
|
356
|
-
if (transformed === null) {
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
368
|
onPropResolve?.({ key, value: transformed });
|
|
360
369
|
if (isMultiProps(transformed)) {
|
|
361
370
|
return getMultiPropsValue(transformed);
|
|
@@ -384,6 +393,9 @@ function createPropsResolver({ transformers, schema: initialSchema, onPropResolv
|
|
|
384
393
|
return null;
|
|
385
394
|
}
|
|
386
395
|
}
|
|
396
|
+
if (value.$$type !== type.key) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
387
399
|
let resolvedValue = value.value;
|
|
388
400
|
if (type.kind === "object") {
|
|
389
401
|
resolvedValue = await resolve({
|
|
@@ -399,7 +411,7 @@ function createPropsResolver({ transformers, schema: initialSchema, onPropResolv
|
|
|
399
411
|
)
|
|
400
412
|
);
|
|
401
413
|
}
|
|
402
|
-
const transformer = transformers
|
|
414
|
+
const transformer = transformers.get(value.$$type);
|
|
403
415
|
if (!transformer) {
|
|
404
416
|
return null;
|
|
405
417
|
}
|
|
@@ -472,6 +484,9 @@ function createStyleWrapper(value = "", wrapper) {
|
|
|
472
484
|
async function propsToCss({ props, resolve, signal }) {
|
|
473
485
|
const transformed = await resolve({ props, signal });
|
|
474
486
|
return Object.entries(transformed).reduce((acc, [propName, propValue]) => {
|
|
487
|
+
if (propValue === null) {
|
|
488
|
+
return acc;
|
|
489
|
+
}
|
|
475
490
|
acc.push(propName + ":" + propValue + ";");
|
|
476
491
|
return acc;
|
|
477
492
|
}, []).join("");
|
|
@@ -499,7 +514,7 @@ function initStylesRenderer() {
|
|
|
499
514
|
listenTo(v1ReadyEvent(), () => {
|
|
500
515
|
let abortController = null;
|
|
501
516
|
const resolve = createPropsResolver({
|
|
502
|
-
transformers: styleTransformersRegistry
|
|
517
|
+
transformers: styleTransformersRegistry,
|
|
503
518
|
schema: getStylesSchema(),
|
|
504
519
|
onPropResolve: enqueueUsedFonts
|
|
505
520
|
});
|
|
@@ -550,6 +565,51 @@ function enqueueUsedFonts({ key, value }) {
|
|
|
550
565
|
import { getWidgetsCache } from "@elementor/editor-elements";
|
|
551
566
|
import { __privateListenTo, v1ReadyEvent as v1ReadyEvent2 } from "@elementor/editor-v1-adapters";
|
|
552
567
|
|
|
568
|
+
// src/renderers/create-dom-renderer.ts
|
|
569
|
+
import { createArrayLoader, createEnvironment } from "@elementor/twing";
|
|
570
|
+
function createDomRenderer() {
|
|
571
|
+
const loader = createArrayLoader({});
|
|
572
|
+
const environment = createEnvironment(loader);
|
|
573
|
+
environment.registerEscapingStrategy(escapeHtmlTag, "html_tag");
|
|
574
|
+
environment.registerEscapingStrategy(escapeURL, "full_url");
|
|
575
|
+
return {
|
|
576
|
+
register: loader.setTemplate,
|
|
577
|
+
render: environment.render
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function escapeHtmlTag(value) {
|
|
581
|
+
const allowedTags = [
|
|
582
|
+
"a",
|
|
583
|
+
"article",
|
|
584
|
+
"aside",
|
|
585
|
+
"button",
|
|
586
|
+
"div",
|
|
587
|
+
"footer",
|
|
588
|
+
"h1",
|
|
589
|
+
"h2",
|
|
590
|
+
"h3",
|
|
591
|
+
"h4",
|
|
592
|
+
"h5",
|
|
593
|
+
"h6",
|
|
594
|
+
"header",
|
|
595
|
+
"main",
|
|
596
|
+
"nav",
|
|
597
|
+
"p",
|
|
598
|
+
"section",
|
|
599
|
+
"span"
|
|
600
|
+
];
|
|
601
|
+
return allowedTags.includes(value) ? value : "div";
|
|
602
|
+
}
|
|
603
|
+
function escapeURL(value) {
|
|
604
|
+
const allowedProtocols = ["http:", "https:", "mailto:", "tel:"];
|
|
605
|
+
try {
|
|
606
|
+
const parsed = new URL(value);
|
|
607
|
+
return allowedProtocols.includes(parsed.protocol) ? value : "";
|
|
608
|
+
} catch {
|
|
609
|
+
return "";
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
553
613
|
// src/legacy/create-element-type.ts
|
|
554
614
|
function createElementType(type) {
|
|
555
615
|
const legacyWindow = window;
|
|
@@ -558,11 +618,11 @@ function createElementType(type) {
|
|
|
558
618
|
return type;
|
|
559
619
|
}
|
|
560
620
|
getView() {
|
|
561
|
-
return
|
|
621
|
+
return createElementViewClassDeclaration();
|
|
562
622
|
}
|
|
563
623
|
};
|
|
564
624
|
}
|
|
565
|
-
function
|
|
625
|
+
function createElementViewClassDeclaration() {
|
|
566
626
|
const legacyWindow = window;
|
|
567
627
|
return class extends legacyWindow.elementor.modules.elements.views.Widget {
|
|
568
628
|
// Dispatch `render` event so the overlay layer will be updated
|
|
@@ -613,16 +673,114 @@ function createElementView() {
|
|
|
613
673
|
};
|
|
614
674
|
}
|
|
615
675
|
|
|
676
|
+
// src/legacy/signalized-process.ts
|
|
677
|
+
function signalizedProcess(signal, steps = []) {
|
|
678
|
+
return {
|
|
679
|
+
then: (cb) => {
|
|
680
|
+
steps.push(cb);
|
|
681
|
+
return signalizedProcess(signal, steps);
|
|
682
|
+
},
|
|
683
|
+
execute: async () => {
|
|
684
|
+
let lastResult;
|
|
685
|
+
for (const step of steps) {
|
|
686
|
+
if (signal.aborted) {
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
lastResult = await step(lastResult, signal);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/legacy/create-templated-element-type.ts
|
|
696
|
+
function createTemplatedElementType({ type, renderer, element }) {
|
|
697
|
+
const legacyWindow = window;
|
|
698
|
+
Object.entries(element.twig_templates).forEach(([key, template]) => {
|
|
699
|
+
renderer.register(key, template);
|
|
700
|
+
});
|
|
701
|
+
const propsResolver = createPropsResolver({
|
|
702
|
+
transformers: settingsTransformersRegistry,
|
|
703
|
+
schema: element.atomic_props_schema
|
|
704
|
+
});
|
|
705
|
+
return class extends legacyWindow.elementor.modules.elements.types.Widget {
|
|
706
|
+
getType() {
|
|
707
|
+
return type;
|
|
708
|
+
}
|
|
709
|
+
getView() {
|
|
710
|
+
return createTemplatedElementViewClassDeclaration({
|
|
711
|
+
type,
|
|
712
|
+
renderer,
|
|
713
|
+
propsResolver,
|
|
714
|
+
baseStylesDictionary: element.base_styles_dictionary,
|
|
715
|
+
templateKey: element.twig_main_template
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function canBeTemplated(element) {
|
|
721
|
+
return !!(element.atomic_props_schema && element.twig_templates && element.twig_main_template && element.base_styles_dictionary);
|
|
722
|
+
}
|
|
723
|
+
function createTemplatedElementViewClassDeclaration({
|
|
724
|
+
type,
|
|
725
|
+
renderer,
|
|
726
|
+
propsResolver: resolveProps,
|
|
727
|
+
templateKey,
|
|
728
|
+
baseStylesDictionary
|
|
729
|
+
}) {
|
|
730
|
+
const BaseView = createElementViewClassDeclaration();
|
|
731
|
+
return class extends BaseView {
|
|
732
|
+
#abortController = null;
|
|
733
|
+
getTemplateType() {
|
|
734
|
+
return "twig";
|
|
735
|
+
}
|
|
736
|
+
renderOnChange() {
|
|
737
|
+
this.render();
|
|
738
|
+
}
|
|
739
|
+
// Overriding Marionette original render method to inject our renderer.
|
|
740
|
+
async _renderTemplate() {
|
|
741
|
+
this.#beforeRenderTemplate();
|
|
742
|
+
this.#abortController?.abort();
|
|
743
|
+
this.#abortController = new AbortController();
|
|
744
|
+
const process = signalizedProcess(this.#abortController.signal).then((_, signal) => {
|
|
745
|
+
const settings = this.model.get("settings").toJSON();
|
|
746
|
+
return resolveProps({
|
|
747
|
+
props: settings,
|
|
748
|
+
signal
|
|
749
|
+
});
|
|
750
|
+
}).then((resolvedSettings) => {
|
|
751
|
+
const context = {
|
|
752
|
+
id: this.model.get("id"),
|
|
753
|
+
type,
|
|
754
|
+
settings: resolvedSettings,
|
|
755
|
+
base_styles: baseStylesDictionary
|
|
756
|
+
};
|
|
757
|
+
return renderer.render(templateKey, context);
|
|
758
|
+
}).then((html) => this.$el.html(html));
|
|
759
|
+
await process.execute();
|
|
760
|
+
this.#afterRenderTemplate();
|
|
761
|
+
}
|
|
762
|
+
// Emulating the original Marionette behavior.
|
|
763
|
+
#beforeRenderTemplate() {
|
|
764
|
+
this.triggerMethod("before:render:template");
|
|
765
|
+
}
|
|
766
|
+
#afterRenderTemplate() {
|
|
767
|
+
this.bindUIElements();
|
|
768
|
+
this.triggerMethod("render:template");
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
616
773
|
// src/legacy/init-legacy-views.ts
|
|
617
774
|
function initLegacyViews() {
|
|
618
775
|
__privateListenTo(v1ReadyEvent2(), () => {
|
|
619
776
|
const config = getWidgetsCache() ?? {};
|
|
620
777
|
const legacyWindow = window;
|
|
778
|
+
const renderer = createDomRenderer();
|
|
621
779
|
Object.entries(config).forEach(([type, element]) => {
|
|
622
780
|
if (!element.atomic) {
|
|
623
781
|
return;
|
|
624
782
|
}
|
|
625
|
-
const ElementType = createElementType(type);
|
|
783
|
+
const ElementType = canBeTemplated(element) ? createTemplatedElementType({ type, renderer, element }) : createElementType(type);
|
|
626
784
|
legacyWindow.elementor.elementsManager.registerElementType(new ElementType());
|
|
627
785
|
});
|
|
628
786
|
});
|