@elementor/editor-canvas 4.0.0 → 4.0.1
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 +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +180 -50
- package/dist/index.mjs +181 -51
- package/package.json +19 -18
- package/src/components/__tests__/style-renderer.test.tsx +4 -0
- package/src/hooks/__tests__/use-style-items.test.ts +68 -0
- package/src/hooks/use-style-items.ts +38 -14
- package/src/init-settings-transformers.ts +2 -0
- package/src/legacy/replacements/base.ts +4 -0
- package/src/legacy/replacements/inline-editing/canvas-inline-editor.tsx +49 -27
- package/src/legacy/replacements/inline-editing/inline-editing-elements.tsx +16 -10
- package/src/legacy/replacements/inline-editing/inline-editing-utils.ts +12 -1
- package/src/legacy/replacements/manager.ts +13 -0
- package/src/legacy/types.ts +3 -0
- package/src/transformers/shared/__tests__/svg-src-transformer.test.ts +184 -0
- package/src/transformers/shared/svg-src-transformer.ts +87 -0
package/dist/index.mjs
CHANGED
|
@@ -910,22 +910,30 @@ function useStyleItems() {
|
|
|
910
910
|
const [styleItems, setStyleItems] = useState3({});
|
|
911
911
|
const styleItemsCacheRef = useRef2(/* @__PURE__ */ new Map());
|
|
912
912
|
const providerAndSubscribers = useMemo4(() => {
|
|
913
|
-
|
|
914
|
-
|
|
913
|
+
const createEmptyCache = () => {
|
|
914
|
+
return { orderedIds: [], itemsById: /* @__PURE__ */ new Map() };
|
|
915
|
+
};
|
|
916
|
+
const getCache = (provider) => {
|
|
917
|
+
const providerKey = safeGetKey(provider);
|
|
918
|
+
if (!providerKey) {
|
|
919
|
+
return createEmptyCache();
|
|
920
|
+
}
|
|
915
921
|
if (!styleItemsCacheRef.current.has(providerKey)) {
|
|
916
|
-
styleItemsCacheRef.current.set(providerKey,
|
|
922
|
+
styleItemsCacheRef.current.set(providerKey, createEmptyCache());
|
|
917
923
|
}
|
|
918
|
-
|
|
919
|
-
|
|
924
|
+
return styleItemsCacheRef.current.get(providerKey);
|
|
925
|
+
};
|
|
926
|
+
return stylesRepository2.getProviders().map(
|
|
927
|
+
(provider) => ({
|
|
920
928
|
provider,
|
|
921
929
|
subscriber: createProviderSubscriber2({
|
|
922
930
|
provider,
|
|
923
931
|
renderStyles,
|
|
924
932
|
setStyleItems,
|
|
925
|
-
|
|
933
|
+
getCache: () => getCache(provider)
|
|
926
934
|
})
|
|
927
|
-
}
|
|
928
|
-
|
|
935
|
+
})
|
|
936
|
+
);
|
|
929
937
|
}, [renderStyles]);
|
|
930
938
|
useEffect6(() => {
|
|
931
939
|
const unsubscribes = providerAndSubscribers.map(
|
|
@@ -965,15 +973,23 @@ function stateSorter({ state: stateA }, { state: stateB }) {
|
|
|
965
973
|
function createBreakpointSorter(breakpointsOrder) {
|
|
966
974
|
return ({ breakpoint: breakpointA }, { breakpoint: breakpointB }) => breakpointsOrder.indexOf(breakpointA) - breakpointsOrder.indexOf(breakpointB);
|
|
967
975
|
}
|
|
968
|
-
function
|
|
976
|
+
function safeGetKey(provider) {
|
|
977
|
+
try {
|
|
978
|
+
return provider.getKey();
|
|
979
|
+
} catch {
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
function createProviderSubscriber2({ provider, renderStyles, setStyleItems, getCache }) {
|
|
969
984
|
return abortPreviousRuns(
|
|
970
985
|
(abortController, previous, current) => signalizedProcess(abortController.signal).then((_, signal) => {
|
|
986
|
+
const cache = getCache();
|
|
971
987
|
const hasDiffInfo = current !== void 0 && previous !== void 0;
|
|
972
988
|
const hasCache = cache.orderedIds.length > 0;
|
|
973
989
|
if (hasDiffInfo && hasCache) {
|
|
974
|
-
return updateItems(previous, current, signal);
|
|
990
|
+
return updateItems(cache, previous, current, signal);
|
|
975
991
|
}
|
|
976
|
-
return createItems(signal);
|
|
992
|
+
return createItems(cache, signal);
|
|
977
993
|
}).then((items) => {
|
|
978
994
|
setStyleItems((prev) => ({
|
|
979
995
|
...prev,
|
|
@@ -981,7 +997,7 @@ function createProviderSubscriber2({ provider, renderStyles, setStyleItems, cach
|
|
|
981
997
|
}));
|
|
982
998
|
}).execute()
|
|
983
999
|
);
|
|
984
|
-
async function updateItems(previous, current, signal) {
|
|
1000
|
+
async function updateItems(cache, previous, current, signal) {
|
|
985
1001
|
const changedIds = getChangedStyleIds(previous, current);
|
|
986
1002
|
cache.orderedIds = provider.actions.all().map((style) => style.id).reverse();
|
|
987
1003
|
if (changedIds.length > 0) {
|
|
@@ -996,7 +1012,7 @@ function createProviderSubscriber2({ provider, renderStyles, setStyleItems, cach
|
|
|
996
1012
|
}
|
|
997
1013
|
return getOrderedItems(cache);
|
|
998
1014
|
}
|
|
999
|
-
async function createItems(signal) {
|
|
1015
|
+
async function createItems(cache, signal) {
|
|
1000
1016
|
const allStyles = provider.actions.all();
|
|
1001
1017
|
const styles = [...allStyles].reverse().map((style) => {
|
|
1002
1018
|
return {
|
|
@@ -1391,14 +1407,74 @@ var plainTransformer = createTransformer((value) => {
|
|
|
1391
1407
|
return value;
|
|
1392
1408
|
});
|
|
1393
1409
|
|
|
1394
|
-
// src/transformers/shared/
|
|
1410
|
+
// src/transformers/shared/svg-src-transformer.ts
|
|
1411
|
+
import DOMPurify from "dompurify";
|
|
1395
1412
|
import { getMediaAttachment as getMediaAttachment2 } from "@elementor/wp-media";
|
|
1413
|
+
var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
|
|
1414
|
+
function processSvgContent(svgText) {
|
|
1415
|
+
const sanitized = DOMPurify.sanitize(svgText, {
|
|
1416
|
+
USE_PROFILES: { svg: true, svgFilters: true }
|
|
1417
|
+
});
|
|
1418
|
+
const parser = new DOMParser();
|
|
1419
|
+
const doc = parser.parseFromString(sanitized, "image/svg+xml");
|
|
1420
|
+
const svgElement = doc.querySelector("svg");
|
|
1421
|
+
if (!svgElement) {
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
svgElement.setAttribute("fill", "currentColor");
|
|
1425
|
+
const existingStyle = svgElement.getAttribute("style") ?? "";
|
|
1426
|
+
const trimmed = existingStyle.trim();
|
|
1427
|
+
const merged = trimmed ? `${trimmed.replace(/;$/, "")}; ${SVG_INLINE_STYLES}` : SVG_INLINE_STYLES;
|
|
1428
|
+
svgElement.setAttribute("style", merged);
|
|
1429
|
+
return svgElement.outerHTML;
|
|
1430
|
+
}
|
|
1431
|
+
async function fetchSvgContent(url, signal) {
|
|
1432
|
+
try {
|
|
1433
|
+
const response = await fetch(url, { signal });
|
|
1434
|
+
if (!response.ok) {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1438
|
+
const isSvg = contentType.includes("svg") || contentType.includes("xml") || url.endsWith(".svg");
|
|
1439
|
+
if (!isSvg) {
|
|
1440
|
+
return null;
|
|
1441
|
+
}
|
|
1442
|
+
return await response.text();
|
|
1443
|
+
} catch {
|
|
1444
|
+
return null;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
function resolveSvgSrcId(id) {
|
|
1448
|
+
if (typeof id !== "number" || id <= 0) {
|
|
1449
|
+
return null;
|
|
1450
|
+
}
|
|
1451
|
+
return id;
|
|
1452
|
+
}
|
|
1453
|
+
var svgSrcTransformer = createTransformer(async (value, { signal }) => {
|
|
1454
|
+
const id = resolveSvgSrcId(value.id);
|
|
1455
|
+
const urlFromValue = typeof value.url === "string" ? value.url : null;
|
|
1456
|
+
let url = urlFromValue;
|
|
1457
|
+
if (id && !urlFromValue) {
|
|
1458
|
+
const attachment = await getMediaAttachment2({ id });
|
|
1459
|
+
url = attachment?.url ?? null;
|
|
1460
|
+
}
|
|
1461
|
+
const resolvedUrl = typeof url === "string" ? url : null;
|
|
1462
|
+
if (!resolvedUrl) {
|
|
1463
|
+
return { html: null, url: null };
|
|
1464
|
+
}
|
|
1465
|
+
const svgText = await fetchSvgContent(resolvedUrl, signal);
|
|
1466
|
+
const html = svgText ? processSvgContent(svgText) : null;
|
|
1467
|
+
return { html, url: resolvedUrl };
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
// src/transformers/shared/video-src-transformer.ts
|
|
1471
|
+
import { getMediaAttachment as getMediaAttachment3 } from "@elementor/wp-media";
|
|
1396
1472
|
var videoSrcTransformer = createTransformer(async (value) => {
|
|
1397
1473
|
const { id, url } = value;
|
|
1398
1474
|
if (!id) {
|
|
1399
1475
|
return { id: null, url };
|
|
1400
1476
|
}
|
|
1401
|
-
const attachment = await
|
|
1477
|
+
const attachment = await getMediaAttachment3({ id });
|
|
1402
1478
|
return {
|
|
1403
1479
|
id,
|
|
1404
1480
|
url: attachment?.url ?? url
|
|
@@ -1407,7 +1483,7 @@ var videoSrcTransformer = createTransformer(async (value) => {
|
|
|
1407
1483
|
|
|
1408
1484
|
// src/init-settings-transformers.ts
|
|
1409
1485
|
function initSettingsTransformers() {
|
|
1410
|
-
settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).registerFallback(plainTransformer);
|
|
1486
|
+
settingsTransformersRegistry.register("classes", createClassesTransformer()).register("link", linkTransformer).register("query", queryTransformer).register("image", imageTransformer).register("image-src", imageSrcTransformer).register("svg-src", svgSrcTransformer).register("video-src", videoSrcTransformer).register("attributes", attributesTransformer).register("date-time", dateTimeTransformer).register("html-v2", htmlV2Transformer).register("html-v3", htmlV3Transformer).registerFallback(plainTransformer);
|
|
1411
1487
|
}
|
|
1412
1488
|
|
|
1413
1489
|
// src/transformers/styles/background-color-overlay-transformer.ts
|
|
@@ -2287,9 +2363,11 @@ function createNestedTemplatedElementView({
|
|
|
2287
2363
|
});
|
|
2288
2364
|
}
|
|
2289
2365
|
|
|
2366
|
+
// src/legacy/replacements/manager.ts
|
|
2367
|
+
import { createRoot } from "react-dom/client";
|
|
2368
|
+
|
|
2290
2369
|
// src/legacy/replacements/inline-editing/inline-editing-elements.tsx
|
|
2291
2370
|
import * as React6 from "react";
|
|
2292
|
-
import { createRoot } from "react-dom/client";
|
|
2293
2371
|
import { getContainer, getElementLabel, getElementType as getElementType2 } from "@elementor/editor-elements";
|
|
2294
2372
|
import {
|
|
2295
2373
|
htmlV3PropTypeUtil as htmlV3PropTypeUtil2,
|
|
@@ -2312,6 +2390,8 @@ var ReplacementBase = class {
|
|
|
2312
2390
|
type;
|
|
2313
2391
|
id;
|
|
2314
2392
|
refreshView;
|
|
2393
|
+
reactRoot;
|
|
2394
|
+
reactContainer;
|
|
2315
2395
|
constructor(settings) {
|
|
2316
2396
|
this.getSetting = settings.getSetting;
|
|
2317
2397
|
this.setSetting = settings.setSetting;
|
|
@@ -2319,6 +2399,8 @@ var ReplacementBase = class {
|
|
|
2319
2399
|
this.type = settings.type;
|
|
2320
2400
|
this.id = settings.id;
|
|
2321
2401
|
this.refreshView = settings.refreshView;
|
|
2402
|
+
this.reactRoot = settings.reactRoot;
|
|
2403
|
+
this.reactContainer = settings.reactContainer;
|
|
2322
2404
|
}
|
|
2323
2405
|
static getTypes() {
|
|
2324
2406
|
return null;
|
|
@@ -2339,7 +2421,7 @@ var ReplacementBase = class {
|
|
|
2339
2421
|
|
|
2340
2422
|
// src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
|
|
2341
2423
|
import * as React5 from "react";
|
|
2342
|
-
import { useEffect as useEffect8, useLayoutEffect, useState as useState5 } from "react";
|
|
2424
|
+
import { useCallback as useCallback2, useEffect as useEffect8, useLayoutEffect, useState as useState5 } from "react";
|
|
2343
2425
|
import { InlineEditor, InlineEditorToolbar } from "@elementor/editor-controls";
|
|
2344
2426
|
import { Box as Box2, ThemeProvider } from "@elementor/ui";
|
|
2345
2427
|
import { autoUpdate as autoUpdate2, flip, FloatingPortal as FloatingPortal2, useFloating as useFloating2 } from "@floating-ui/react";
|
|
@@ -2369,7 +2451,8 @@ var INLINE_EDITING_PROPERTY_PER_TYPE = {
|
|
|
2369
2451
|
"e-button": "text",
|
|
2370
2452
|
"e-form-label": "text",
|
|
2371
2453
|
"e-heading": "title",
|
|
2372
|
-
"e-paragraph": "paragraph"
|
|
2454
|
+
"e-paragraph": "paragraph",
|
|
2455
|
+
"e-form-submit-button": "text"
|
|
2373
2456
|
};
|
|
2374
2457
|
var getInlineEditorElement = (elementWrapper, expectedTag) => {
|
|
2375
2458
|
return !expectedTag ? null : elementWrapper.querySelector(expectedTag);
|
|
@@ -2387,6 +2470,11 @@ var useOnClickOutsideIframe = (handleUnmount) => {
|
|
|
2387
2470
|
};
|
|
2388
2471
|
var useRenderToolbar = (ownerDocument, id) => {
|
|
2389
2472
|
const [anchor, setAnchor] = useState4(null);
|
|
2473
|
+
useEffect7(() => {
|
|
2474
|
+
if (!anchor) {
|
|
2475
|
+
removeToolbarAnchor(ownerDocument, id);
|
|
2476
|
+
}
|
|
2477
|
+
}, [anchor, ownerDocument, id]);
|
|
2390
2478
|
const onSelectionEnd = (view) => {
|
|
2391
2479
|
const hasSelection = !view.state.selection.empty;
|
|
2392
2480
|
removeToolbarAnchor(ownerDocument, id);
|
|
@@ -2396,7 +2484,10 @@ var useRenderToolbar = (ownerDocument, id) => {
|
|
|
2396
2484
|
setAnchor(null);
|
|
2397
2485
|
}
|
|
2398
2486
|
};
|
|
2399
|
-
|
|
2487
|
+
const clearAnchor = useCallback(() => {
|
|
2488
|
+
setAnchor(null);
|
|
2489
|
+
}, []);
|
|
2490
|
+
return { onSelectionEnd, anchor, clearAnchor };
|
|
2400
2491
|
};
|
|
2401
2492
|
var createAnchorBasedOnSelection = (ownerDocument, id) => {
|
|
2402
2493
|
const frameWindow = ownerDocument.defaultView;
|
|
@@ -2463,46 +2554,59 @@ var horizontalShifterMiddleware = {
|
|
|
2463
2554
|
};
|
|
2464
2555
|
|
|
2465
2556
|
// src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
|
|
2466
|
-
var EDITOR_WRAPPER_SELECTOR = "inline-editor-wrapper";
|
|
2467
2557
|
var CanvasInlineEditor = ({
|
|
2468
2558
|
elementClasses,
|
|
2469
2559
|
initialValue,
|
|
2470
2560
|
expectedTag,
|
|
2471
2561
|
rootElement,
|
|
2562
|
+
contentElement,
|
|
2472
2563
|
id,
|
|
2473
2564
|
setValue,
|
|
2474
|
-
|
|
2565
|
+
requestDestroy
|
|
2475
2566
|
}) => {
|
|
2567
|
+
const [active, setActive] = useState5(true);
|
|
2476
2568
|
const [editor, setEditor] = useState5(null);
|
|
2477
|
-
const { onSelectionEnd, anchor: toolbarAnchor } = useRenderToolbar(rootElement.ownerDocument, id);
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2569
|
+
const { onSelectionEnd, anchor: toolbarAnchor, clearAnchor } = useRenderToolbar(rootElement.ownerDocument, id);
|
|
2570
|
+
useEffect8(() => {
|
|
2571
|
+
if (!active) {
|
|
2572
|
+
clearAnchor();
|
|
2573
|
+
requestDestroy();
|
|
2574
|
+
}
|
|
2575
|
+
}, [active, clearAnchor, requestDestroy]);
|
|
2576
|
+
const dismiss = useCallback2(() => {
|
|
2577
|
+
setEditor(null);
|
|
2578
|
+
setActive(false);
|
|
2579
|
+
}, []);
|
|
2580
|
+
useOnClickOutsideIframe(dismiss);
|
|
2581
|
+
useEffect8(() => {
|
|
2582
|
+
const ownerDocument = contentElement.ownerDocument;
|
|
2583
|
+
const handleClickAway = (event) => {
|
|
2584
|
+
if (contentElement.contains(event.target)) {
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
dismiss();
|
|
2588
|
+
};
|
|
2589
|
+
ownerDocument.addEventListener("mousedown", handleClickAway);
|
|
2590
|
+
return () => ownerDocument.removeEventListener("mousedown", handleClickAway);
|
|
2591
|
+
}, [contentElement, dismiss]);
|
|
2592
|
+
if (!active) {
|
|
2593
|
+
return null;
|
|
2594
|
+
}
|
|
2595
|
+
return /* @__PURE__ */ React5.createElement(ThemeProvider, null, /* @__PURE__ */ React5.createElement(InlineEditingOverlay, { expectedTag, rootElement, id }), /* @__PURE__ */ React5.createElement(
|
|
2492
2596
|
InlineEditor,
|
|
2493
2597
|
{
|
|
2494
2598
|
onEditorCreate: setEditor,
|
|
2599
|
+
mountElement: contentElement,
|
|
2495
2600
|
editorProps: {
|
|
2496
2601
|
attributes: {
|
|
2497
|
-
style: "outline: none;
|
|
2602
|
+
style: "outline: none; display: inherit; justify-content: inherit; align-items: inherit; flex-direction: inherit; text-align: inherit;"
|
|
2498
2603
|
}
|
|
2499
2604
|
},
|
|
2500
2605
|
elementClasses,
|
|
2501
2606
|
value: initialValue,
|
|
2502
2607
|
setValue,
|
|
2503
|
-
onBlur,
|
|
2608
|
+
onBlur: dismiss,
|
|
2504
2609
|
autofocus: true,
|
|
2505
|
-
expectedTag,
|
|
2506
2610
|
onSelectionEnd
|
|
2507
2611
|
}
|
|
2508
2612
|
), toolbarAnchor && editor && /* @__PURE__ */ React5.createElement(InlineEditingToolbar, { anchor: toolbarAnchor, editor, id }));
|
|
@@ -2531,7 +2635,18 @@ var InlineEditingToolbar = ({ anchor, editor, id }) => {
|
|
|
2531
2635
|
refs.setReference(anchor);
|
|
2532
2636
|
return () => refs.setReference(null);
|
|
2533
2637
|
}, [anchor, refs]);
|
|
2534
|
-
return /* @__PURE__ */ React5.createElement(FloatingPortal2, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React5.createElement(
|
|
2638
|
+
return /* @__PURE__ */ React5.createElement(FloatingPortal2, { id: CANVAS_WRAPPER_ID }, /* @__PURE__ */ React5.createElement(
|
|
2639
|
+
Box2,
|
|
2640
|
+
{
|
|
2641
|
+
ref: refs.setFloating,
|
|
2642
|
+
role: "presentation",
|
|
2643
|
+
style: {
|
|
2644
|
+
...floatingStyles,
|
|
2645
|
+
pointerEvents: "none"
|
|
2646
|
+
}
|
|
2647
|
+
},
|
|
2648
|
+
/* @__PURE__ */ React5.createElement(InlineEditorToolbar, { editor, elementId: id })
|
|
2649
|
+
));
|
|
2535
2650
|
};
|
|
2536
2651
|
|
|
2537
2652
|
// src/legacy/replacements/inline-editing/inline-editing-eligibility.ts
|
|
@@ -2565,8 +2680,8 @@ var isInlineEditingAllowed = ({ rawValue, propTypeFromSchema }) => {
|
|
|
2565
2680
|
// src/legacy/replacements/inline-editing/inline-editing-elements.tsx
|
|
2566
2681
|
var HISTORY_DEBOUNCE_WAIT = 800;
|
|
2567
2682
|
var InlineEditingReplacement = class extends ReplacementBase {
|
|
2568
|
-
inlineEditorRoot = null;
|
|
2569
2683
|
handlerAttached = false;
|
|
2684
|
+
editing = false;
|
|
2570
2685
|
getReplacementKey() {
|
|
2571
2686
|
return "inline-editing";
|
|
2572
2687
|
}
|
|
@@ -2574,7 +2689,7 @@ var InlineEditingReplacement = class extends ReplacementBase {
|
|
|
2574
2689
|
return Object.keys(INLINE_EDITING_PROPERTY_PER_TYPE);
|
|
2575
2690
|
}
|
|
2576
2691
|
isEditingModeActive() {
|
|
2577
|
-
return
|
|
2692
|
+
return this.editing;
|
|
2578
2693
|
}
|
|
2579
2694
|
shouldRenderReplacement() {
|
|
2580
2695
|
return this.isInlineEditingEligible() && getCurrentEditMode() === "edit";
|
|
@@ -2617,8 +2732,8 @@ var InlineEditingReplacement = class extends ReplacementBase {
|
|
|
2617
2732
|
resetInlineEditorRoot() {
|
|
2618
2733
|
this.element.removeEventListener("click", this.handleRenderInlineEditor);
|
|
2619
2734
|
this.handlerAttached = false;
|
|
2620
|
-
this.
|
|
2621
|
-
this.
|
|
2735
|
+
this.reactRoot.render(null);
|
|
2736
|
+
this.editing = false;
|
|
2622
2737
|
}
|
|
2623
2738
|
unmountInlineEditor() {
|
|
2624
2739
|
this.resetInlineEditorRoot();
|
|
@@ -2729,12 +2844,16 @@ var InlineEditingReplacement = class extends ReplacementBase {
|
|
|
2729
2844
|
if (this.isEditingModeActive()) {
|
|
2730
2845
|
this.resetInlineEditorRoot();
|
|
2731
2846
|
}
|
|
2732
|
-
const
|
|
2847
|
+
const contentElement = this.element.children?.[0];
|
|
2848
|
+
if (!contentElement) {
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
const elementClasses = contentElement.classList.toString();
|
|
2733
2852
|
const propValue = this.getExtractedContentValue();
|
|
2734
2853
|
const expectedTag = this.getExpectedTag();
|
|
2735
|
-
|
|
2736
|
-
this.
|
|
2737
|
-
this.
|
|
2854
|
+
contentElement.innerHTML = "";
|
|
2855
|
+
this.editing = true;
|
|
2856
|
+
this.reactRoot.render(
|
|
2738
2857
|
/* @__PURE__ */ React6.createElement(
|
|
2739
2858
|
CanvasInlineEditor,
|
|
2740
2859
|
{
|
|
@@ -2742,9 +2861,10 @@ var InlineEditingReplacement = class extends ReplacementBase {
|
|
|
2742
2861
|
initialValue: propValue,
|
|
2743
2862
|
expectedTag,
|
|
2744
2863
|
rootElement: this.element,
|
|
2864
|
+
contentElement,
|
|
2745
2865
|
id: this.id,
|
|
2746
2866
|
setValue: this.setContentValue.bind(this),
|
|
2747
|
-
|
|
2867
|
+
requestDestroy: this.unmountInlineEditor.bind(this)
|
|
2748
2868
|
}
|
|
2749
2869
|
)
|
|
2750
2870
|
);
|
|
@@ -2773,16 +2893,24 @@ var createViewWithReplacements = (options) => {
|
|
|
2773
2893
|
return class extends TemplatedView {
|
|
2774
2894
|
#replacement = null;
|
|
2775
2895
|
#config;
|
|
2896
|
+
#reactContainer;
|
|
2897
|
+
#reactRoot;
|
|
2776
2898
|
constructor(...args) {
|
|
2777
2899
|
super(...args);
|
|
2778
2900
|
const settings = this.model.get("settings");
|
|
2901
|
+
this.#reactContainer = this.el.ownerDocument.createElement("div");
|
|
2902
|
+
this.#reactContainer.style.display = "none";
|
|
2903
|
+
this.el.ownerDocument.body.appendChild(this.#reactContainer);
|
|
2904
|
+
this.#reactRoot = createRoot(this.#reactContainer);
|
|
2779
2905
|
this.#config = {
|
|
2780
2906
|
getSetting: settings.get.bind(settings),
|
|
2781
2907
|
setSetting: settings.set.bind(settings),
|
|
2782
2908
|
element: this.el,
|
|
2783
2909
|
type: this?.model?.get("widgetType") ?? this.container?.model?.get("elType") ?? null,
|
|
2784
2910
|
id: this?.model?.get("id") ?? null,
|
|
2785
|
-
refreshView: this.refreshView.bind(this)
|
|
2911
|
+
refreshView: this.refreshView.bind(this),
|
|
2912
|
+
reactRoot: this.#reactRoot,
|
|
2913
|
+
reactContainer: this.#reactContainer
|
|
2786
2914
|
};
|
|
2787
2915
|
}
|
|
2788
2916
|
refreshView() {
|
|
@@ -2803,6 +2931,8 @@ var createViewWithReplacements = (options) => {
|
|
|
2803
2931
|
}
|
|
2804
2932
|
onDestroy() {
|
|
2805
2933
|
this.#triggerAltMethod("onDestroy");
|
|
2934
|
+
this.#reactRoot.unmount();
|
|
2935
|
+
this.#reactContainer.remove();
|
|
2806
2936
|
}
|
|
2807
2937
|
_afterRender() {
|
|
2808
2938
|
this.#triggerAltMethod("_afterRender");
|
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.0.
|
|
4
|
+
"version": "4.0.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -37,24 +37,25 @@
|
|
|
37
37
|
"react-dom": "^18.3.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@elementor/editor": "4.0.
|
|
41
|
-
"
|
|
42
|
-
"@elementor/editor-
|
|
43
|
-
"@elementor/editor-
|
|
44
|
-
"@elementor/editor-
|
|
45
|
-
"@elementor/editor-
|
|
46
|
-
"@elementor/editor-
|
|
47
|
-
"@elementor/editor-
|
|
48
|
-
"@elementor/editor-
|
|
49
|
-
"@elementor/editor-
|
|
50
|
-
"@elementor/editor-styles
|
|
51
|
-
"@elementor/editor-
|
|
52
|
-
"@elementor/editor-
|
|
53
|
-
"@elementor/
|
|
54
|
-
"@elementor/
|
|
40
|
+
"@elementor/editor": "4.0.1",
|
|
41
|
+
"dompurify": "^3.2.6",
|
|
42
|
+
"@elementor/editor-controls": "4.0.1",
|
|
43
|
+
"@elementor/editor-documents": "4.0.1",
|
|
44
|
+
"@elementor/editor-elements": "4.0.1",
|
|
45
|
+
"@elementor/editor-interactions": "4.0.1",
|
|
46
|
+
"@elementor/editor-mcp": "4.0.1",
|
|
47
|
+
"@elementor/editor-notifications": "4.0.1",
|
|
48
|
+
"@elementor/editor-props": "4.0.1",
|
|
49
|
+
"@elementor/editor-responsive": "4.0.1",
|
|
50
|
+
"@elementor/editor-styles": "4.0.1",
|
|
51
|
+
"@elementor/editor-styles-repository": "4.0.1",
|
|
52
|
+
"@elementor/editor-ui": "4.0.1",
|
|
53
|
+
"@elementor/editor-v1-adapters": "4.0.1",
|
|
54
|
+
"@elementor/schema": "4.0.1",
|
|
55
|
+
"@elementor/twing": "4.0.1",
|
|
55
56
|
"@elementor/ui": "1.36.17",
|
|
56
|
-
"@elementor/utils": "4.0.
|
|
57
|
-
"@elementor/wp-media": "4.0.
|
|
57
|
+
"@elementor/utils": "4.0.1",
|
|
58
|
+
"@elementor/wp-media": "4.0.1",
|
|
58
59
|
"@floating-ui/react": "^0.27.5",
|
|
59
60
|
"@wordpress/i18n": "^5.13.0"
|
|
60
61
|
},
|
|
@@ -13,6 +13,10 @@ jest.mock( '@elementor/editor-v1-adapters', () => ( {
|
|
|
13
13
|
commandEndEvent: jest.fn(),
|
|
14
14
|
} ) );
|
|
15
15
|
|
|
16
|
+
jest.mock( '@elementor/editor-documents', () => ( {
|
|
17
|
+
getCurrentDocument: jest.fn().mockReturnValue( { id: 1 } ),
|
|
18
|
+
} ) );
|
|
19
|
+
|
|
16
20
|
jest.mock( '@elementor/ui', () => ( {
|
|
17
21
|
Portal: jest.fn( ( { children } ) => <div data-testid="portal">{ children }</div> ),
|
|
18
22
|
} ) );
|
|
@@ -340,6 +340,74 @@ describe( 'useStyleItems', () => {
|
|
|
340
340
|
expect( breakpointOrderAfterUpdate ).toEqual( [ 'desktop', 'tablet', 'mobile' ] );
|
|
341
341
|
} );
|
|
342
342
|
|
|
343
|
+
it( 'should recover and render styles when a provider key becomes available after initial failure', async () => {
|
|
344
|
+
// Arrange.
|
|
345
|
+
let shouldThrow = true;
|
|
346
|
+
|
|
347
|
+
const dynamicKey: () => string = () => {
|
|
348
|
+
if ( shouldThrow ) {
|
|
349
|
+
throw new Error( 'Document not ready' );
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return 'late-provider';
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const failingThenSucceedingProvider = createMockStylesProvider(
|
|
356
|
+
{
|
|
357
|
+
key: dynamicKey,
|
|
358
|
+
priority: 2,
|
|
359
|
+
},
|
|
360
|
+
[ createMockStyleDefinition( { id: 'late-style1' } ), createMockStyleDefinition( { id: 'late-style2' } ) ]
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const stableProvider = createMockStylesProvider(
|
|
364
|
+
{
|
|
365
|
+
key: 'stable-provider',
|
|
366
|
+
priority: 1,
|
|
367
|
+
},
|
|
368
|
+
[
|
|
369
|
+
createMockStyleDefinition( { id: 'stable-style1' } ),
|
|
370
|
+
createMockStyleDefinition( { id: 'stable-style2' } ),
|
|
371
|
+
]
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
jest.mocked( stylesRepository ).getProviders.mockReturnValue( [
|
|
375
|
+
failingThenSucceedingProvider,
|
|
376
|
+
stableProvider,
|
|
377
|
+
] );
|
|
378
|
+
|
|
379
|
+
let attachPreviewCallback: () => Promise< void >;
|
|
380
|
+
|
|
381
|
+
jest.mocked( registerDataHook ).mockImplementation( ( position, command, callback ) => {
|
|
382
|
+
if ( command === 'editor/documents/attach-preview' && position === 'after' ) {
|
|
383
|
+
attachPreviewCallback = callback as never;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return null as never;
|
|
387
|
+
} );
|
|
388
|
+
|
|
389
|
+
// Act.
|
|
390
|
+
const { result } = renderHook( () => useStyleItems() );
|
|
391
|
+
|
|
392
|
+
// Assert - hook should not crash, should return empty initially.
|
|
393
|
+
expect( result.current ).toEqual( [] );
|
|
394
|
+
|
|
395
|
+
// Act - simulate document becoming ready, then trigger attach-preview.
|
|
396
|
+
shouldThrow = false;
|
|
397
|
+
|
|
398
|
+
await act( async () => {
|
|
399
|
+
await attachPreviewCallback?.();
|
|
400
|
+
} );
|
|
401
|
+
|
|
402
|
+
// Assert - both providers' styles should render in correct priority order.
|
|
403
|
+
expect( result.current ).toEqual( [
|
|
404
|
+
{ id: 'stable-style2', breakpoint: 'desktop' },
|
|
405
|
+
{ id: 'stable-style1', breakpoint: 'desktop' },
|
|
406
|
+
{ id: 'late-style2', breakpoint: 'desktop' },
|
|
407
|
+
{ id: 'late-style1', breakpoint: 'desktop' },
|
|
408
|
+
] );
|
|
409
|
+
} );
|
|
410
|
+
|
|
343
411
|
it( 'should only re-render changed styles on differential update', async () => {
|
|
344
412
|
// Arrange.
|
|
345
413
|
const renderStylesMock = jest.fn().mockImplementation( ( { styles } ) =>
|
|
@@ -36,25 +36,35 @@ export function useStyleItems() {
|
|
|
36
36
|
const styleItemsCacheRef = useRef< Map< string, StyleItemsCache > >( new Map() );
|
|
37
37
|
|
|
38
38
|
const providerAndSubscribers = useMemo( () => {
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
const createEmptyCache = () => {
|
|
40
|
+
return { orderedIds: [], itemsById: new Map() };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getCache = ( provider: StylesProvider ): StyleItemsCache => {
|
|
44
|
+
const providerKey = safeGetKey( provider );
|
|
45
|
+
|
|
46
|
+
if ( ! providerKey ) {
|
|
47
|
+
return createEmptyCache();
|
|
48
|
+
}
|
|
41
49
|
|
|
42
50
|
if ( ! styleItemsCacheRef.current.has( providerKey ) ) {
|
|
43
|
-
styleItemsCacheRef.current.set( providerKey,
|
|
51
|
+
styleItemsCacheRef.current.set( providerKey, createEmptyCache() );
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
return styleItemsCacheRef.current.get( providerKey ) as StyleItemsCache;
|
|
55
|
+
};
|
|
47
56
|
|
|
48
|
-
|
|
57
|
+
return stylesRepository.getProviders().map(
|
|
58
|
+
( provider ): ProviderAndSubscriber => ( {
|
|
49
59
|
provider,
|
|
50
60
|
subscriber: createProviderSubscriber( {
|
|
51
61
|
provider,
|
|
52
62
|
renderStyles,
|
|
53
63
|
setStyleItems,
|
|
54
|
-
|
|
64
|
+
getCache: () => getCache( provider ),
|
|
55
65
|
} ),
|
|
56
|
-
}
|
|
57
|
-
|
|
66
|
+
} )
|
|
67
|
+
);
|
|
58
68
|
}, [ renderStyles ] );
|
|
59
69
|
|
|
60
70
|
useEffect( () => {
|
|
@@ -122,25 +132,34 @@ function createBreakpointSorter( breakpointsOrder: BreakpointId[] ) {
|
|
|
122
132
|
breakpointsOrder.indexOf( breakpointB as BreakpointId );
|
|
123
133
|
}
|
|
124
134
|
|
|
135
|
+
function safeGetKey( provider: StylesProvider ): string | null {
|
|
136
|
+
try {
|
|
137
|
+
return provider.getKey();
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
125
143
|
type CreateProviderSubscriberArgs = {
|
|
126
144
|
provider: StylesProvider;
|
|
127
145
|
renderStyles: StyleRenderer;
|
|
128
146
|
setStyleItems: Dispatch< SetStateAction< ProviderAndStyleItemsMap > >;
|
|
129
|
-
|
|
147
|
+
getCache: () => StyleItemsCache;
|
|
130
148
|
};
|
|
131
149
|
|
|
132
|
-
function createProviderSubscriber( { provider, renderStyles, setStyleItems,
|
|
150
|
+
function createProviderSubscriber( { provider, renderStyles, setStyleItems, getCache }: CreateProviderSubscriberArgs ) {
|
|
133
151
|
return abortPreviousRuns( ( abortController, previous?: StylesCollection, current?: StylesCollection ) =>
|
|
134
152
|
signalizedProcess( abortController.signal )
|
|
135
153
|
.then( ( _, signal ) => {
|
|
154
|
+
const cache = getCache();
|
|
136
155
|
const hasDiffInfo = current !== undefined && previous !== undefined;
|
|
137
156
|
const hasCache = cache.orderedIds.length > 0;
|
|
138
157
|
|
|
139
158
|
if ( hasDiffInfo && hasCache ) {
|
|
140
|
-
return updateItems( previous, current, signal );
|
|
159
|
+
return updateItems( cache, previous, current, signal );
|
|
141
160
|
}
|
|
142
161
|
|
|
143
|
-
return createItems( signal );
|
|
162
|
+
return createItems( cache, signal );
|
|
144
163
|
} )
|
|
145
164
|
.then( ( items ) => {
|
|
146
165
|
setStyleItems( ( prev ) => ( {
|
|
@@ -151,7 +170,12 @@ function createProviderSubscriber( { provider, renderStyles, setStyleItems, cach
|
|
|
151
170
|
.execute()
|
|
152
171
|
);
|
|
153
172
|
|
|
154
|
-
async function updateItems(
|
|
173
|
+
async function updateItems(
|
|
174
|
+
cache: StyleItemsCache,
|
|
175
|
+
previous: StylesCollection,
|
|
176
|
+
current: StylesCollection,
|
|
177
|
+
signal: AbortSignal
|
|
178
|
+
) {
|
|
155
179
|
const changedIds = getChangedStyleIds( previous, current );
|
|
156
180
|
|
|
157
181
|
cache.orderedIds = provider.actions
|
|
@@ -178,7 +202,7 @@ function createProviderSubscriber( { provider, renderStyles, setStyleItems, cach
|
|
|
178
202
|
return getOrderedItems( cache );
|
|
179
203
|
}
|
|
180
204
|
|
|
181
|
-
async function createItems( signal: AbortSignal ) {
|
|
205
|
+
async function createItems( cache: StyleItemsCache, signal: AbortSignal ) {
|
|
182
206
|
const allStyles = provider.actions.all();
|
|
183
207
|
|
|
184
208
|
const styles = [ ...allStyles ].reverse().map( ( style ) => {
|