@elementor/editor-canvas 4.3.0-beta1 → 4.3.0-beta3
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 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +66 -80
- package/dist/index.mjs +64 -75
- package/package.json +20 -20
- package/src/__tests__/flex-transformer.test.ts +11 -11
- package/src/__tests__/prop-types.ts +11 -0
- package/src/legacy/create-nested-templated-element-type.ts +31 -1
- package/src/legacy/types.ts +1 -0
- package/src/transformers/shared/__tests__/icon-transformer.test.ts +133 -8
- package/src/transformers/shared/icon-transformer.ts +40 -76
- package/src/transformers/styles/flex-transformer.ts +11 -32
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2107,6 +2107,9 @@ var timeRangeTransformer = createTransformer((value) => {
|
|
|
2107
2107
|
};
|
|
2108
2108
|
});
|
|
2109
2109
|
|
|
2110
|
+
// src/transformers/shared/icon-transformer.ts
|
|
2111
|
+
var import_editor_controls = require("@elementor/editor-controls");
|
|
2112
|
+
|
|
2110
2113
|
// src/transformers/shared/process-svg-content.ts
|
|
2111
2114
|
var import_dompurify2 = __toESM(require("dompurify"));
|
|
2112
2115
|
var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
|
|
@@ -2129,78 +2132,56 @@ function processSvgContent(svgText) {
|
|
|
2129
2132
|
}
|
|
2130
2133
|
|
|
2131
2134
|
// src/transformers/shared/icon-transformer.ts
|
|
2132
|
-
var
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
path: 4
|
|
2136
|
-
};
|
|
2137
|
-
var fontAwesomeJsonCache = /* @__PURE__ */ new Map();
|
|
2135
|
+
var EMPTY_ICON_RESULT = { html: null, url: null };
|
|
2136
|
+
var ICON_SVG_SIZE = "100%";
|
|
2137
|
+
var ICON_SVG_OVERFLOW = "visible";
|
|
2138
2138
|
var iconTransformer = createTransformer(async (value, { signal }) => {
|
|
2139
2139
|
const iconValue = typeof value.value === "string" ? value.value : null;
|
|
2140
2140
|
const library = typeof value.library === "string" ? value.library : null;
|
|
2141
2141
|
if (!iconValue || !library) {
|
|
2142
|
-
return
|
|
2142
|
+
return EMPTY_ICON_RESULT;
|
|
2143
2143
|
}
|
|
2144
|
-
const iconName =
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
return { html: null, url: null };
|
|
2144
|
+
const iconName = (0, import_editor_controls.getFontAwesome7IconName)(iconValue);
|
|
2145
|
+
if (!iconName) {
|
|
2146
|
+
return EMPTY_ICON_RESULT;
|
|
2148
2147
|
}
|
|
2149
|
-
const
|
|
2150
|
-
const iconData = icons?.[iconName];
|
|
2148
|
+
const iconData = await (0, import_editor_controls.resolveFontAwesome7Icon)(library, iconName, signal);
|
|
2151
2149
|
if (!iconData) {
|
|
2152
|
-
return
|
|
2150
|
+
return EMPTY_ICON_RESULT;
|
|
2153
2151
|
}
|
|
2154
2152
|
const svgText = buildFontAwesomeSvg(iconData);
|
|
2155
|
-
|
|
2153
|
+
if (!svgText) {
|
|
2154
|
+
return EMPTY_ICON_RESULT;
|
|
2155
|
+
}
|
|
2156
|
+
const html = processIconSvgContent(svgText);
|
|
2156
2157
|
return { html, url: null };
|
|
2157
2158
|
});
|
|
2158
|
-
function
|
|
2159
|
-
|
|
2160
|
-
return match?.[1] ?? null;
|
|
2161
|
-
}
|
|
2162
|
-
function getFontAwesomeJsonFileName(library) {
|
|
2163
|
-
if (!library.startsWith("fa-")) {
|
|
2159
|
+
function buildFontAwesomeSvg(iconData) {
|
|
2160
|
+
if (iconData.paths.length === 0) {
|
|
2164
2161
|
return null;
|
|
2165
2162
|
}
|
|
2166
|
-
|
|
2163
|
+
const pathMarkup = iconData.paths.map((path) => `<path d="${escapeSvgPath(path)}"></path>`).join("");
|
|
2164
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${iconData.width} ${iconData.height}">${pathMarkup}</svg>`;
|
|
2167
2165
|
}
|
|
2168
|
-
function
|
|
2169
|
-
|
|
2170
|
-
return typeof assetsUrl === "string" && assetsUrl !== "" ? assetsUrl : null;
|
|
2166
|
+
function escapeSvgPath(path) {
|
|
2167
|
+
return path.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
2171
2168
|
}
|
|
2172
|
-
|
|
2173
|
-
const
|
|
2174
|
-
if (
|
|
2175
|
-
return cached;
|
|
2176
|
-
}
|
|
2177
|
-
const icons = await loadFontAwesomeIcons(jsonFileName, signal);
|
|
2178
|
-
if (icons) {
|
|
2179
|
-
fontAwesomeJsonCache.set(jsonFileName, icons);
|
|
2180
|
-
}
|
|
2181
|
-
return icons;
|
|
2182
|
-
}
|
|
2183
|
-
async function loadFontAwesomeIcons(jsonFileName, signal) {
|
|
2184
|
-
const assetsUrl = getAssetsBaseUrl();
|
|
2185
|
-
if (!assetsUrl) {
|
|
2169
|
+
function processIconSvgContent(svgText) {
|
|
2170
|
+
const html = processSvgContent(svgText);
|
|
2171
|
+
if (!html) {
|
|
2186
2172
|
return null;
|
|
2187
2173
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
}
|
|
2193
|
-
const data = await response.json();
|
|
2194
|
-
return data.icons ?? null;
|
|
2195
|
-
} catch {
|
|
2174
|
+
const parser = new DOMParser();
|
|
2175
|
+
const doc = parser.parseFromString(html, "image/svg+xml");
|
|
2176
|
+
const svgElement = doc.querySelector("svg");
|
|
2177
|
+
if (!svgElement) {
|
|
2196
2178
|
return null;
|
|
2197
2179
|
}
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"><path d="${path}"></path></svg>`;
|
|
2180
|
+
svgElement.setAttribute("aria-hidden", "true");
|
|
2181
|
+
svgElement.style.setProperty("width", ICON_SVG_SIZE);
|
|
2182
|
+
svgElement.style.setProperty("height", ICON_SVG_SIZE);
|
|
2183
|
+
svgElement.style.setProperty("overflow", ICON_SVG_OVERFLOW);
|
|
2184
|
+
return svgElement.outerHTML;
|
|
2204
2185
|
}
|
|
2205
2186
|
|
|
2206
2187
|
// src/transformers/shared/image-src-transformer.ts
|
|
@@ -2446,6 +2427,10 @@ var mapToFilterFunctionString = (value) => {
|
|
|
2446
2427
|
};
|
|
2447
2428
|
|
|
2448
2429
|
// src/transformers/styles/flex-transformer.ts
|
|
2430
|
+
var DEFAULT_FLEX_GROW = 0;
|
|
2431
|
+
var DEFAULT_FLEX_SHRINK = 1;
|
|
2432
|
+
var DEFAULT_FLEX_BASIS = "auto";
|
|
2433
|
+
var formatBasis = (basis) => typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis;
|
|
2449
2434
|
var flexTransformer = createTransformer((value) => {
|
|
2450
2435
|
const grow = value.flexGrow;
|
|
2451
2436
|
const shrink = value.flexShrink;
|
|
@@ -2456,28 +2441,10 @@ var flexTransformer = createTransformer((value) => {
|
|
|
2456
2441
|
if (!hasGrow && !hasShrink && !hasBasis) {
|
|
2457
2442
|
return null;
|
|
2458
2443
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
return `${grow} ${shrink}`;
|
|
2464
|
-
}
|
|
2465
|
-
if (hasGrow && !hasShrink && hasBasis) {
|
|
2466
|
-
return `${grow} 1 ${typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis}`;
|
|
2467
|
-
}
|
|
2468
|
-
if (!hasGrow && hasShrink && hasBasis) {
|
|
2469
|
-
return `0 ${shrink} ${typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis}`;
|
|
2470
|
-
}
|
|
2471
|
-
if (hasGrow && !hasShrink && !hasBasis) {
|
|
2472
|
-
return `${grow}`;
|
|
2473
|
-
}
|
|
2474
|
-
if (!hasGrow && hasShrink && !hasBasis) {
|
|
2475
|
-
return `0 ${shrink}`;
|
|
2476
|
-
}
|
|
2477
|
-
if (!hasGrow && !hasShrink && hasBasis) {
|
|
2478
|
-
return `0 1 ${typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis}`;
|
|
2479
|
-
}
|
|
2480
|
-
return null;
|
|
2444
|
+
const growOut = hasGrow ? grow : DEFAULT_FLEX_GROW;
|
|
2445
|
+
const shrinkOut = hasShrink ? shrink : DEFAULT_FLEX_SHRINK;
|
|
2446
|
+
const basisOut = hasBasis ? formatBasis(basis) : DEFAULT_FLEX_BASIS;
|
|
2447
|
+
return `${growOut} ${shrinkOut} ${basisOut}`;
|
|
2481
2448
|
});
|
|
2482
2449
|
|
|
2483
2450
|
// src/transformers/styles/font-family-transformer.ts
|
|
@@ -2609,10 +2576,10 @@ var transformSkewTransformer = createTransformer((value) => {
|
|
|
2609
2576
|
});
|
|
2610
2577
|
|
|
2611
2578
|
// src/transformers/styles/transition-transformer.ts
|
|
2612
|
-
var
|
|
2579
|
+
var import_editor_controls2 = require("@elementor/editor-controls");
|
|
2613
2580
|
var getAllowedProperties = () => {
|
|
2614
2581
|
const allowedProperties = /* @__PURE__ */ new Set();
|
|
2615
|
-
|
|
2582
|
+
import_editor_controls2.transitionProperties.forEach((category) => {
|
|
2616
2583
|
category.properties.forEach((property) => {
|
|
2617
2584
|
allowedProperties.add(property.value);
|
|
2618
2585
|
});
|
|
@@ -3055,6 +3022,7 @@ function createTemplatedElementView({
|
|
|
3055
3022
|
}
|
|
3056
3023
|
|
|
3057
3024
|
// src/legacy/create-nested-templated-element-type.ts
|
|
3025
|
+
var STYLES_REFERENCE_UNTRACKED = /* @__PURE__ */ Symbol("styles-reference-untracked");
|
|
3058
3026
|
function canBeNestedTemplated(element) {
|
|
3059
3027
|
return canBeTemplated(element) && "support_nesting" in element && !!element.support_nesting;
|
|
3060
3028
|
}
|
|
@@ -3114,6 +3082,7 @@ function createNestedTemplatedElementView({
|
|
|
3114
3082
|
return AtomicElementBaseView.extend({
|
|
3115
3083
|
_abortController: null,
|
|
3116
3084
|
_lastResolvedSettingsHash: null,
|
|
3085
|
+
_lastRenderedStyles: STYLES_REFERENCE_UNTRACKED,
|
|
3117
3086
|
_domUpdateWasSkipped: false,
|
|
3118
3087
|
template: false,
|
|
3119
3088
|
attributes() {
|
|
@@ -3150,6 +3119,23 @@ function createNestedTemplatedElementView({
|
|
|
3150
3119
|
this._initAlpine();
|
|
3151
3120
|
});
|
|
3152
3121
|
this.model.trigger("render:complete");
|
|
3122
|
+
this._notifyStylesChanged();
|
|
3123
|
+
},
|
|
3124
|
+
// `document/elements/create` fires before the nested template has fully painted, so the
|
|
3125
|
+
// styles provider rebuilds CSS too early and misses the new local class IDs. Dispatching
|
|
3126
|
+
// ELEMENT_STYLE_CHANGE_EVENT here — after the template and all children are in the DOM —
|
|
3127
|
+
// gives the provider a second chance to regenerate CSS against the live class names.
|
|
3128
|
+
//
|
|
3129
|
+
// The reference-equality guard prevents a parent re-render from emitting one event per
|
|
3130
|
+
// unchanged descendant. Use a sentinel for the initial state — otherwise elements with no
|
|
3131
|
+
// local styles (undefined === undefined) would never notify, and descendants like e-heading
|
|
3132
|
+
// that rely on this post-paint refresh would lose their CSS after detach.
|
|
3133
|
+
_notifyStylesChanged() {
|
|
3134
|
+
const styles = this.model.get("styles");
|
|
3135
|
+
if (this._lastRenderedStyles !== STYLES_REFERENCE_UNTRACKED && styles === this._lastRenderedStyles) {
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
this._lastRenderedStyles = styles;
|
|
3153
3139
|
window.dispatchEvent(new CustomEvent(import_editor_elements6.ELEMENT_STYLE_CHANGE_EVENT));
|
|
3154
3140
|
},
|
|
3155
3141
|
async _renderTemplate() {
|
|
@@ -3449,7 +3435,7 @@ var ReplacementBase = class {
|
|
|
3449
3435
|
// src/legacy/replacements/inline-editing/canvas-inline-editor.tsx
|
|
3450
3436
|
var React10 = __toESM(require("react"));
|
|
3451
3437
|
var import_react18 = require("react");
|
|
3452
|
-
var
|
|
3438
|
+
var import_editor_controls3 = require("@elementor/editor-controls");
|
|
3453
3439
|
var import_ui5 = require("@elementor/ui");
|
|
3454
3440
|
var import_react19 = require("@floating-ui/react");
|
|
3455
3441
|
|
|
@@ -3620,7 +3606,7 @@ var CanvasInlineEditor = ({
|
|
|
3620
3606
|
return null;
|
|
3621
3607
|
}
|
|
3622
3608
|
return /* @__PURE__ */ React10.createElement(import_ui5.ThemeProvider, null, /* @__PURE__ */ React10.createElement(InlineEditingOverlay, { expectedTag, rootElement, id }), /* @__PURE__ */ React10.createElement(
|
|
3623
|
-
|
|
3609
|
+
import_editor_controls3.InlineEditor,
|
|
3624
3610
|
{
|
|
3625
3611
|
onEditorCreate: setEditor,
|
|
3626
3612
|
mountElement: contentElement,
|
|
@@ -3672,7 +3658,7 @@ var InlineEditingToolbar = ({ anchor, editor, id }) => {
|
|
|
3672
3658
|
pointerEvents: "none"
|
|
3673
3659
|
}
|
|
3674
3660
|
},
|
|
3675
|
-
/* @__PURE__ */ React10.createElement(
|
|
3661
|
+
/* @__PURE__ */ React10.createElement(import_editor_controls3.InlineEditorToolbar, { editor, elementId: id })
|
|
3676
3662
|
));
|
|
3677
3663
|
};
|
|
3678
3664
|
|
package/dist/index.mjs
CHANGED
|
@@ -2059,6 +2059,12 @@ var timeRangeTransformer = createTransformer((value) => {
|
|
|
2059
2059
|
};
|
|
2060
2060
|
});
|
|
2061
2061
|
|
|
2062
|
+
// src/transformers/shared/icon-transformer.ts
|
|
2063
|
+
import {
|
|
2064
|
+
getFontAwesome7IconName,
|
|
2065
|
+
resolveFontAwesome7Icon
|
|
2066
|
+
} from "@elementor/editor-controls";
|
|
2067
|
+
|
|
2062
2068
|
// src/transformers/shared/process-svg-content.ts
|
|
2063
2069
|
import DOMPurify2 from "dompurify";
|
|
2064
2070
|
var SVG_INLINE_STYLES = "width: 100%; height: 100%; overflow: unset;";
|
|
@@ -2081,78 +2087,56 @@ function processSvgContent(svgText) {
|
|
|
2081
2087
|
}
|
|
2082
2088
|
|
|
2083
2089
|
// src/transformers/shared/icon-transformer.ts
|
|
2084
|
-
var
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
path: 4
|
|
2088
|
-
};
|
|
2089
|
-
var fontAwesomeJsonCache = /* @__PURE__ */ new Map();
|
|
2090
|
+
var EMPTY_ICON_RESULT = { html: null, url: null };
|
|
2091
|
+
var ICON_SVG_SIZE = "100%";
|
|
2092
|
+
var ICON_SVG_OVERFLOW = "visible";
|
|
2090
2093
|
var iconTransformer = createTransformer(async (value, { signal }) => {
|
|
2091
2094
|
const iconValue = typeof value.value === "string" ? value.value : null;
|
|
2092
2095
|
const library = typeof value.library === "string" ? value.library : null;
|
|
2093
2096
|
if (!iconValue || !library) {
|
|
2094
|
-
return
|
|
2097
|
+
return EMPTY_ICON_RESULT;
|
|
2095
2098
|
}
|
|
2096
|
-
const iconName =
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
return { html: null, url: null };
|
|
2099
|
+
const iconName = getFontAwesome7IconName(iconValue);
|
|
2100
|
+
if (!iconName) {
|
|
2101
|
+
return EMPTY_ICON_RESULT;
|
|
2100
2102
|
}
|
|
2101
|
-
const
|
|
2102
|
-
const iconData = icons?.[iconName];
|
|
2103
|
+
const iconData = await resolveFontAwesome7Icon(library, iconName, signal);
|
|
2103
2104
|
if (!iconData) {
|
|
2104
|
-
return
|
|
2105
|
+
return EMPTY_ICON_RESULT;
|
|
2105
2106
|
}
|
|
2106
2107
|
const svgText = buildFontAwesomeSvg(iconData);
|
|
2107
|
-
|
|
2108
|
+
if (!svgText) {
|
|
2109
|
+
return EMPTY_ICON_RESULT;
|
|
2110
|
+
}
|
|
2111
|
+
const html = processIconSvgContent(svgText);
|
|
2108
2112
|
return { html, url: null };
|
|
2109
2113
|
});
|
|
2110
|
-
function
|
|
2111
|
-
|
|
2112
|
-
return match?.[1] ?? null;
|
|
2113
|
-
}
|
|
2114
|
-
function getFontAwesomeJsonFileName(library) {
|
|
2115
|
-
if (!library.startsWith("fa-")) {
|
|
2114
|
+
function buildFontAwesomeSvg(iconData) {
|
|
2115
|
+
if (iconData.paths.length === 0) {
|
|
2116
2116
|
return null;
|
|
2117
2117
|
}
|
|
2118
|
-
|
|
2118
|
+
const pathMarkup = iconData.paths.map((path) => `<path d="${escapeSvgPath(path)}"></path>`).join("");
|
|
2119
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${iconData.width} ${iconData.height}">${pathMarkup}</svg>`;
|
|
2119
2120
|
}
|
|
2120
|
-
function
|
|
2121
|
-
|
|
2122
|
-
return typeof assetsUrl === "string" && assetsUrl !== "" ? assetsUrl : null;
|
|
2121
|
+
function escapeSvgPath(path) {
|
|
2122
|
+
return path.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
2123
2123
|
}
|
|
2124
|
-
|
|
2125
|
-
const
|
|
2126
|
-
if (
|
|
2127
|
-
return cached;
|
|
2128
|
-
}
|
|
2129
|
-
const icons = await loadFontAwesomeIcons(jsonFileName, signal);
|
|
2130
|
-
if (icons) {
|
|
2131
|
-
fontAwesomeJsonCache.set(jsonFileName, icons);
|
|
2132
|
-
}
|
|
2133
|
-
return icons;
|
|
2134
|
-
}
|
|
2135
|
-
async function loadFontAwesomeIcons(jsonFileName, signal) {
|
|
2136
|
-
const assetsUrl = getAssetsBaseUrl();
|
|
2137
|
-
if (!assetsUrl) {
|
|
2124
|
+
function processIconSvgContent(svgText) {
|
|
2125
|
+
const html = processSvgContent(svgText);
|
|
2126
|
+
if (!html) {
|
|
2138
2127
|
return null;
|
|
2139
2128
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
}
|
|
2145
|
-
const data = await response.json();
|
|
2146
|
-
return data.icons ?? null;
|
|
2147
|
-
} catch {
|
|
2129
|
+
const parser = new DOMParser();
|
|
2130
|
+
const doc = parser.parseFromString(html, "image/svg+xml");
|
|
2131
|
+
const svgElement = doc.querySelector("svg");
|
|
2132
|
+
if (!svgElement) {
|
|
2148
2133
|
return null;
|
|
2149
2134
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"><path d="${path}"></path></svg>`;
|
|
2135
|
+
svgElement.setAttribute("aria-hidden", "true");
|
|
2136
|
+
svgElement.style.setProperty("width", ICON_SVG_SIZE);
|
|
2137
|
+
svgElement.style.setProperty("height", ICON_SVG_SIZE);
|
|
2138
|
+
svgElement.style.setProperty("overflow", ICON_SVG_OVERFLOW);
|
|
2139
|
+
return svgElement.outerHTML;
|
|
2156
2140
|
}
|
|
2157
2141
|
|
|
2158
2142
|
// src/transformers/shared/image-src-transformer.ts
|
|
@@ -2398,6 +2382,10 @@ var mapToFilterFunctionString = (value) => {
|
|
|
2398
2382
|
};
|
|
2399
2383
|
|
|
2400
2384
|
// src/transformers/styles/flex-transformer.ts
|
|
2385
|
+
var DEFAULT_FLEX_GROW = 0;
|
|
2386
|
+
var DEFAULT_FLEX_SHRINK = 1;
|
|
2387
|
+
var DEFAULT_FLEX_BASIS = "auto";
|
|
2388
|
+
var formatBasis = (basis) => typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis;
|
|
2401
2389
|
var flexTransformer = createTransformer((value) => {
|
|
2402
2390
|
const grow = value.flexGrow;
|
|
2403
2391
|
const shrink = value.flexShrink;
|
|
@@ -2408,28 +2396,10 @@ var flexTransformer = createTransformer((value) => {
|
|
|
2408
2396
|
if (!hasGrow && !hasShrink && !hasBasis) {
|
|
2409
2397
|
return null;
|
|
2410
2398
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
return `${grow} ${shrink}`;
|
|
2416
|
-
}
|
|
2417
|
-
if (hasGrow && !hasShrink && hasBasis) {
|
|
2418
|
-
return `${grow} 1 ${typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis}`;
|
|
2419
|
-
}
|
|
2420
|
-
if (!hasGrow && hasShrink && hasBasis) {
|
|
2421
|
-
return `0 ${shrink} ${typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis}`;
|
|
2422
|
-
}
|
|
2423
|
-
if (hasGrow && !hasShrink && !hasBasis) {
|
|
2424
|
-
return `${grow}`;
|
|
2425
|
-
}
|
|
2426
|
-
if (!hasGrow && hasShrink && !hasBasis) {
|
|
2427
|
-
return `0 ${shrink}`;
|
|
2428
|
-
}
|
|
2429
|
-
if (!hasGrow && !hasShrink && hasBasis) {
|
|
2430
|
-
return `0 1 ${typeof basis === "object" && basis.size !== void 0 ? `${basis.size}${basis.unit || ""}` : basis}`;
|
|
2431
|
-
}
|
|
2432
|
-
return null;
|
|
2399
|
+
const growOut = hasGrow ? grow : DEFAULT_FLEX_GROW;
|
|
2400
|
+
const shrinkOut = hasShrink ? shrink : DEFAULT_FLEX_SHRINK;
|
|
2401
|
+
const basisOut = hasBasis ? formatBasis(basis) : DEFAULT_FLEX_BASIS;
|
|
2402
|
+
return `${growOut} ${shrinkOut} ${basisOut}`;
|
|
2433
2403
|
});
|
|
2434
2404
|
|
|
2435
2405
|
// src/transformers/styles/font-family-transformer.ts
|
|
@@ -3012,6 +2982,7 @@ function createTemplatedElementView({
|
|
|
3012
2982
|
}
|
|
3013
2983
|
|
|
3014
2984
|
// src/legacy/create-nested-templated-element-type.ts
|
|
2985
|
+
var STYLES_REFERENCE_UNTRACKED = /* @__PURE__ */ Symbol("styles-reference-untracked");
|
|
3015
2986
|
function canBeNestedTemplated(element) {
|
|
3016
2987
|
return canBeTemplated(element) && "support_nesting" in element && !!element.support_nesting;
|
|
3017
2988
|
}
|
|
@@ -3071,6 +3042,7 @@ function createNestedTemplatedElementView({
|
|
|
3071
3042
|
return AtomicElementBaseView.extend({
|
|
3072
3043
|
_abortController: null,
|
|
3073
3044
|
_lastResolvedSettingsHash: null,
|
|
3045
|
+
_lastRenderedStyles: STYLES_REFERENCE_UNTRACKED,
|
|
3074
3046
|
_domUpdateWasSkipped: false,
|
|
3075
3047
|
template: false,
|
|
3076
3048
|
attributes() {
|
|
@@ -3107,6 +3079,23 @@ function createNestedTemplatedElementView({
|
|
|
3107
3079
|
this._initAlpine();
|
|
3108
3080
|
});
|
|
3109
3081
|
this.model.trigger("render:complete");
|
|
3082
|
+
this._notifyStylesChanged();
|
|
3083
|
+
},
|
|
3084
|
+
// `document/elements/create` fires before the nested template has fully painted, so the
|
|
3085
|
+
// styles provider rebuilds CSS too early and misses the new local class IDs. Dispatching
|
|
3086
|
+
// ELEMENT_STYLE_CHANGE_EVENT here — after the template and all children are in the DOM —
|
|
3087
|
+
// gives the provider a second chance to regenerate CSS against the live class names.
|
|
3088
|
+
//
|
|
3089
|
+
// The reference-equality guard prevents a parent re-render from emitting one event per
|
|
3090
|
+
// unchanged descendant. Use a sentinel for the initial state — otherwise elements with no
|
|
3091
|
+
// local styles (undefined === undefined) would never notify, and descendants like e-heading
|
|
3092
|
+
// that rely on this post-paint refresh would lose their CSS after detach.
|
|
3093
|
+
_notifyStylesChanged() {
|
|
3094
|
+
const styles = this.model.get("styles");
|
|
3095
|
+
if (this._lastRenderedStyles !== STYLES_REFERENCE_UNTRACKED && styles === this._lastRenderedStyles) {
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3098
|
+
this._lastRenderedStyles = styles;
|
|
3110
3099
|
window.dispatchEvent(new CustomEvent(ELEMENT_STYLE_CHANGE_EVENT2));
|
|
3111
3100
|
},
|
|
3112
3101
|
async _renderTemplate() {
|
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-beta3",
|
|
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-beta3",
|
|
41
|
+
"@elementor/editor-controls": "4.3.0-beta3",
|
|
42
|
+
"@elementor/editor-documents": "4.3.0-beta3",
|
|
43
|
+
"@elementor/editor-elements": "4.3.0-beta3",
|
|
44
|
+
"@elementor/editor-interactions": "4.3.0-beta3",
|
|
45
|
+
"@elementor/editor-mcp": "4.3.0-beta3",
|
|
46
|
+
"@elementor/editor-notifications": "4.3.0-beta3",
|
|
47
|
+
"@elementor/editor-props": "4.3.0-beta3",
|
|
48
|
+
"@elementor/editor-responsive": "4.3.0-beta3",
|
|
49
|
+
"@elementor/editor-styles": "4.3.0-beta3",
|
|
50
|
+
"@elementor/editor-styles-repository": "4.3.0-beta3",
|
|
51
|
+
"@elementor/editor-ui": "4.3.0-beta3",
|
|
52
|
+
"@elementor/editor-v1-adapters": "4.3.0-beta3",
|
|
53
|
+
"@elementor/events": "4.3.0-beta3",
|
|
54
|
+
"@elementor/http-client": "4.3.0-beta3",
|
|
55
|
+
"@elementor/schema": "4.3.0-beta3",
|
|
56
|
+
"@elementor/twing": "4.3.0-beta3",
|
|
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-beta3",
|
|
59
|
+
"@elementor/wp-media": "4.3.0-beta3",
|
|
60
60
|
"@floating-ui/react": "^0.27.5",
|
|
61
61
|
"@wordpress/i18n": "^5.13.0",
|
|
62
62
|
"dompurify": "^3.2.6"
|
|
@@ -68,7 +68,7 @@ describe( 'flexTransformer', () => {
|
|
|
68
68
|
} );
|
|
69
69
|
|
|
70
70
|
describe( 'when only two values are provided', () => {
|
|
71
|
-
it( 'should return "grow shrink" when grow and shrink are set', () => {
|
|
71
|
+
it( 'should return "grow shrink auto" when grow and shrink are set', () => {
|
|
72
72
|
// Arrange.
|
|
73
73
|
const value: Flex = {
|
|
74
74
|
flexGrow: 1,
|
|
@@ -80,7 +80,7 @@ describe( 'flexTransformer', () => {
|
|
|
80
80
|
const result = flexTransformer( value, { key: 'flex' } );
|
|
81
81
|
|
|
82
82
|
// Assert.
|
|
83
|
-
expect( result ).toBe( '1 2' );
|
|
83
|
+
expect( result ).toBe( '1 2 auto' );
|
|
84
84
|
} );
|
|
85
85
|
|
|
86
86
|
it( 'should return "grow 1 basis" when grow and basis are set', () => {
|
|
@@ -115,7 +115,7 @@ describe( 'flexTransformer', () => {
|
|
|
115
115
|
} );
|
|
116
116
|
|
|
117
117
|
describe( 'when only one value is provided', () => {
|
|
118
|
-
it( 'should return "grow" when only grow is set', () => {
|
|
118
|
+
it( 'should return "grow 1 auto" when only grow is set', () => {
|
|
119
119
|
// Arrange.
|
|
120
120
|
const value: Flex = {
|
|
121
121
|
flexGrow: 1,
|
|
@@ -127,10 +127,10 @@ describe( 'flexTransformer', () => {
|
|
|
127
127
|
const result = flexTransformer( value, { key: 'flex' } );
|
|
128
128
|
|
|
129
129
|
// Assert.
|
|
130
|
-
expect( result ).toBe( '1' );
|
|
130
|
+
expect( result ).toBe( '1 1 auto' );
|
|
131
131
|
} );
|
|
132
132
|
|
|
133
|
-
it( 'should return "0 shrink" when only shrink is set', () => {
|
|
133
|
+
it( 'should return "0 shrink auto" when only shrink is set', () => {
|
|
134
134
|
// Arrange.
|
|
135
135
|
const value: Flex = {
|
|
136
136
|
flexGrow: null,
|
|
@@ -142,7 +142,7 @@ describe( 'flexTransformer', () => {
|
|
|
142
142
|
const result = flexTransformer( value, { key: 'flex' } );
|
|
143
143
|
|
|
144
144
|
// Assert.
|
|
145
|
-
expect( result ).toBe( '0 2' );
|
|
145
|
+
expect( result ).toBe( '0 2 auto' );
|
|
146
146
|
} );
|
|
147
147
|
|
|
148
148
|
it( 'should return "0 1 basis" when only basis is set', () => {
|
|
@@ -238,8 +238,8 @@ describe( 'flexTransformer', () => {
|
|
|
238
238
|
} );
|
|
239
239
|
} );
|
|
240
240
|
|
|
241
|
-
describe( 'CSS
|
|
242
|
-
it( 'should
|
|
241
|
+
describe( 'CSS initial values for omitted longhands', () => {
|
|
242
|
+
it( 'should default shrink and basis when only grow is set', () => {
|
|
243
243
|
// Arrange.
|
|
244
244
|
const value: Flex = {
|
|
245
245
|
flexGrow: 2,
|
|
@@ -251,10 +251,10 @@ describe( 'flexTransformer', () => {
|
|
|
251
251
|
const result = flexTransformer( value, { key: 'flex' } );
|
|
252
252
|
|
|
253
253
|
// Assert.
|
|
254
|
-
expect( result ).toBe( '2' );
|
|
254
|
+
expect( result ).toBe( '2 1 auto' );
|
|
255
255
|
} );
|
|
256
256
|
|
|
257
|
-
it( 'should
|
|
257
|
+
it( 'should default basis when grow and shrink are set', () => {
|
|
258
258
|
// Arrange.
|
|
259
259
|
const value: Flex = {
|
|
260
260
|
flexGrow: 1,
|
|
@@ -266,7 +266,7 @@ describe( 'flexTransformer', () => {
|
|
|
266
266
|
const result = flexTransformer( value, { key: 'flex' } );
|
|
267
267
|
|
|
268
268
|
// Assert.
|
|
269
|
-
expect( result ).toBe( '1 2' );
|
|
269
|
+
expect( result ).toBe( '1 2 auto' );
|
|
270
270
|
} );
|
|
271
271
|
} );
|
|
272
272
|
} );
|
|
@@ -36,6 +36,17 @@ export function linkPropType() {
|
|
|
36
36
|
} );
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export function iconPropType() {
|
|
40
|
+
return createMockPropType( {
|
|
41
|
+
kind: 'object',
|
|
42
|
+
key: 'icon',
|
|
43
|
+
shape: {
|
|
44
|
+
value: stringPropType(),
|
|
45
|
+
library: stringPropType(),
|
|
46
|
+
},
|
|
47
|
+
} );
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
export function imagePropType() {
|
|
40
51
|
return createMockPropType( {
|
|
41
52
|
kind: 'object',
|
|
@@ -12,7 +12,13 @@ import {
|
|
|
12
12
|
setupTwigRenderer,
|
|
13
13
|
waitForChildrenToComplete,
|
|
14
14
|
} from './twig-rendering-utils';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
type ElementModel,
|
|
17
|
+
type ElementType,
|
|
18
|
+
type ElementView,
|
|
19
|
+
type LegacyWindow,
|
|
20
|
+
type NestedTemplatedElementViewClass,
|
|
21
|
+
} from './types';
|
|
16
22
|
|
|
17
23
|
export type NestedTemplatedElementConfig = TemplatedElementConfig & {
|
|
18
24
|
allowed_child_types?: string[];
|
|
@@ -21,6 +27,8 @@ export type NestedTemplatedElementConfig = TemplatedElementConfig & {
|
|
|
21
27
|
|
|
22
28
|
export type ModelExtensions = Record< string, unknown >;
|
|
23
29
|
|
|
30
|
+
const STYLES_REFERENCE_UNTRACKED = Symbol( 'styles-reference-untracked' );
|
|
31
|
+
|
|
24
32
|
export type CreateNestedTemplatedElementTypeOptions = {
|
|
25
33
|
type: string;
|
|
26
34
|
renderer: DomRenderer;
|
|
@@ -108,6 +116,7 @@ export function createNestedTemplatedElementView( {
|
|
|
108
116
|
return AtomicElementBaseView.extend( {
|
|
109
117
|
_abortController: null as AbortController | null,
|
|
110
118
|
_lastResolvedSettingsHash: null as string | null,
|
|
119
|
+
_lastRenderedStyles: STYLES_REFERENCE_UNTRACKED as typeof STYLES_REFERENCE_UNTRACKED | ElementModel[ 'styles' ],
|
|
111
120
|
_domUpdateWasSkipped: false,
|
|
112
121
|
|
|
113
122
|
template: false,
|
|
@@ -164,6 +173,27 @@ export function createNestedTemplatedElementView( {
|
|
|
164
173
|
} );
|
|
165
174
|
|
|
166
175
|
this.model.trigger( 'render:complete' );
|
|
176
|
+
this._notifyStylesChanged();
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
// `document/elements/create` fires before the nested template has fully painted, so the
|
|
180
|
+
// styles provider rebuilds CSS too early and misses the new local class IDs. Dispatching
|
|
181
|
+
// ELEMENT_STYLE_CHANGE_EVENT here — after the template and all children are in the DOM —
|
|
182
|
+
// gives the provider a second chance to regenerate CSS against the live class names.
|
|
183
|
+
//
|
|
184
|
+
// The reference-equality guard prevents a parent re-render from emitting one event per
|
|
185
|
+
// unchanged descendant. Use a sentinel for the initial state — otherwise elements with no
|
|
186
|
+
// local styles (undefined === undefined) would never notify, and descendants like e-heading
|
|
187
|
+
// that rely on this post-paint refresh would lose their CSS after detach.
|
|
188
|
+
_notifyStylesChanged() {
|
|
189
|
+
const styles = this.model.get( 'styles' );
|
|
190
|
+
|
|
191
|
+
if ( this._lastRenderedStyles !== STYLES_REFERENCE_UNTRACKED && styles === this._lastRenderedStyles ) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this._lastRenderedStyles = styles;
|
|
196
|
+
|
|
167
197
|
window.dispatchEvent( new CustomEvent( ELEMENT_STYLE_CHANGE_EVENT ) );
|
|
168
198
|
},
|
|
169
199
|
|
package/src/legacy/types.ts
CHANGED
|
@@ -229,6 +229,7 @@ export type ElementModel = {
|
|
|
229
229
|
elType: string;
|
|
230
230
|
settings: BackboneModel< Props >;
|
|
231
231
|
editor_settings: Record< string, unknown >;
|
|
232
|
+
styles?: V1ElementModelProps[ 'styles' ];
|
|
232
233
|
widgetType: string;
|
|
233
234
|
editSettings?: BackboneModel< { inactive?: boolean } >;
|
|
234
235
|
elements?: BackboneCollection< ElementModel >;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resetFontAwesome7IconsCache } from '@elementor/editor-controls';
|
|
2
|
+
import { iconPropTypeUtil, stringPropTypeUtil } from '@elementor/editor-props';
|
|
3
|
+
|
|
4
|
+
import { iconPropType } from '../../../__tests__/prop-types';
|
|
5
|
+
import { initSettingsTransformers } from '../../../init-settings-transformers';
|
|
6
|
+
import { createPropsResolver } from '../../../renderers/create-props-resolver';
|
|
7
|
+
import { settingsTransformersRegistry } from '../../../settings-transformers-registry';
|
|
2
8
|
|
|
3
9
|
const STAR_PATH = 'M0 0h100v100H0z';
|
|
4
|
-
const STAR_WIDTH =
|
|
10
|
+
const STAR_WIDTH = 576;
|
|
5
11
|
const STAR_HEIGHT = 512;
|
|
6
12
|
|
|
7
13
|
const mockFetch = ( body: unknown, ok = true ) => {
|
|
@@ -11,17 +17,46 @@ const mockFetch = ( body: unknown, ok = true ) => {
|
|
|
11
17
|
} );
|
|
12
18
|
};
|
|
13
19
|
|
|
20
|
+
function createSavedIcon( iconClass: string, library: string ) {
|
|
21
|
+
return iconPropTypeUtil.create( {
|
|
22
|
+
value: stringPropTypeUtil.create( iconClass ),
|
|
23
|
+
library: stringPropTypeUtil.create( library ),
|
|
24
|
+
} );
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function resolveSavedIcon( iconClass: string, library: string ) {
|
|
28
|
+
const resolve = createPropsResolver( {
|
|
29
|
+
transformers: settingsTransformersRegistry,
|
|
30
|
+
schema: { svg: iconPropType() },
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
const result = await resolve( {
|
|
34
|
+
props: {
|
|
35
|
+
svg: createSavedIcon( iconClass, library ),
|
|
36
|
+
},
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
return result.svg;
|
|
40
|
+
}
|
|
41
|
+
|
|
14
42
|
describe( 'iconTransformer', () => {
|
|
15
43
|
const originalElementorCommon = window.elementorCommon;
|
|
16
44
|
|
|
17
45
|
beforeEach( () => {
|
|
18
46
|
jest.clearAllMocks();
|
|
19
|
-
|
|
47
|
+
resetFontAwesome7IconsCache();
|
|
48
|
+
initSettingsTransformers();
|
|
20
49
|
window.elementorCommon = {
|
|
21
50
|
config: {
|
|
22
51
|
urls: {
|
|
23
52
|
assets: 'https://example.com/assets/',
|
|
24
53
|
},
|
|
54
|
+
fontAwesome: {
|
|
55
|
+
v7: {
|
|
56
|
+
jsonFiles: [ 'solid', 'regular', 'brands' ],
|
|
57
|
+
jsonBaseUrl: 'https://example.com/assets/lib/font-awesome-7/json/',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
25
60
|
},
|
|
26
61
|
};
|
|
27
62
|
} );
|
|
@@ -40,10 +75,10 @@ describe( 'iconTransformer', () => {
|
|
|
40
75
|
} );
|
|
41
76
|
|
|
42
77
|
// Act.
|
|
43
|
-
const result = await
|
|
78
|
+
const result = await resolveSavedIcon( 'fas fa-star', 'fa-solid' );
|
|
44
79
|
|
|
45
80
|
// Assert.
|
|
46
|
-
expect( global.fetch ).toHaveBeenCalledWith( 'https://example.com/assets/lib/font-awesome/json/solid.json', {
|
|
81
|
+
expect( global.fetch ).toHaveBeenCalledWith( 'https://example.com/assets/lib/font-awesome-7/json/solid.json', {
|
|
47
82
|
signal: undefined,
|
|
48
83
|
} );
|
|
49
84
|
expect( result ).toEqual( {
|
|
@@ -51,6 +86,45 @@ describe( 'iconTransformer', () => {
|
|
|
51
86
|
url: null,
|
|
52
87
|
} );
|
|
53
88
|
expect( ( result as { html: string } ).html ).toContain( 'fill="currentColor"' );
|
|
89
|
+
expect( ( result as { html: string } ).html ).toContain( `viewBox="0 0 ${ STAR_WIDTH } ${ STAR_HEIGHT }"` );
|
|
90
|
+
expect( ( result as { html: string } ).html ).toContain( 'aria-hidden="true"' );
|
|
91
|
+
expect( ( result as { html: string } ).html ).toContain( 'overflow: visible' );
|
|
92
|
+
} );
|
|
93
|
+
|
|
94
|
+
it( 'resolves icons by alias name', async () => {
|
|
95
|
+
// Arrange.
|
|
96
|
+
mockFetch( {
|
|
97
|
+
icons: {
|
|
98
|
+
headphones: [ 448, 512, [ 'headphones-simple' ], 'f025', 'M0 0' ],
|
|
99
|
+
},
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
// Act.
|
|
103
|
+
const result = await resolveSavedIcon( 'fas fa-headphones-simple', 'fa-solid' );
|
|
104
|
+
|
|
105
|
+
// Assert.
|
|
106
|
+
expect( result ).toEqual( {
|
|
107
|
+
html: expect.stringContaining( 'M0 0' ),
|
|
108
|
+
url: null,
|
|
109
|
+
} );
|
|
110
|
+
} );
|
|
111
|
+
|
|
112
|
+
it( 'renders multiple paths when the icon definition contains an array', async () => {
|
|
113
|
+
// Arrange.
|
|
114
|
+
mockFetch( {
|
|
115
|
+
icons: {
|
|
116
|
+
slash: [ 640, 640, [], 'f715', [ 'M0 0', 'M10 10' ] ],
|
|
117
|
+
},
|
|
118
|
+
} );
|
|
119
|
+
|
|
120
|
+
// Act.
|
|
121
|
+
const result = await resolveSavedIcon( 'fas fa-slash', 'fa-solid' );
|
|
122
|
+
|
|
123
|
+
// Assert.
|
|
124
|
+
const html = ( result as { html: string } ).html;
|
|
125
|
+
expect( html.match( /<path/g ) ).toHaveLength( 2 );
|
|
126
|
+
expect( html ).toContain( 'M0 0' );
|
|
127
|
+
expect( html ).toContain( 'M10 10' );
|
|
54
128
|
} );
|
|
55
129
|
|
|
56
130
|
it( 'returns null html when the icon cannot be resolved', async () => {
|
|
@@ -58,17 +132,68 @@ describe( 'iconTransformer', () => {
|
|
|
58
132
|
mockFetch( { icons: {} } );
|
|
59
133
|
|
|
60
134
|
// Act.
|
|
61
|
-
const result = await
|
|
135
|
+
const result = await resolveSavedIcon( 'fas fa-missing', 'fa-solid' );
|
|
62
136
|
|
|
63
137
|
// Assert.
|
|
64
138
|
expect( result ).toEqual( { html: null, url: null } );
|
|
65
139
|
} );
|
|
66
140
|
|
|
67
|
-
it( '
|
|
141
|
+
it( 'fetches json from the localized font awesome 7 base url', async () => {
|
|
142
|
+
// Arrange.
|
|
143
|
+
const jsonBaseUrl = 'https://cdn.example.com/fa7/json/';
|
|
144
|
+
window.elementorCommon = {
|
|
145
|
+
config: {
|
|
146
|
+
fontAwesome: {
|
|
147
|
+
v7: {
|
|
148
|
+
jsonFiles: [ 'solid', 'regular', 'brands' ],
|
|
149
|
+
jsonBaseUrl,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
mockFetch( {
|
|
155
|
+
icons: {
|
|
156
|
+
star: [ STAR_WIDTH, STAR_HEIGHT, [], 'f005', STAR_PATH ],
|
|
157
|
+
},
|
|
158
|
+
} );
|
|
159
|
+
|
|
68
160
|
// Act.
|
|
69
|
-
|
|
161
|
+
await resolveSavedIcon( 'fas fa-star', 'fa-solid' );
|
|
70
162
|
|
|
71
163
|
// Assert.
|
|
164
|
+
expect( global.fetch ).toHaveBeenCalledWith( `${ jsonBaseUrl }solid.json`, {
|
|
165
|
+
signal: undefined,
|
|
166
|
+
} );
|
|
167
|
+
} );
|
|
168
|
+
|
|
169
|
+
it( 'does not fetch json when library is not an allowed fa7 file', async () => {
|
|
170
|
+
// Arrange.
|
|
171
|
+
mockFetch( { icons: {} } );
|
|
172
|
+
|
|
173
|
+
// Act.
|
|
174
|
+
const result = await resolveSavedIcon( 'fas fa-star', 'fa-../../wp-config' );
|
|
175
|
+
|
|
176
|
+
// Assert.
|
|
177
|
+
expect( global.fetch ).not.toHaveBeenCalled();
|
|
72
178
|
expect( result ).toEqual( { html: null, url: null } );
|
|
73
179
|
} );
|
|
180
|
+
|
|
181
|
+
it( 'returns null html when value or library is missing', async () => {
|
|
182
|
+
// Act.
|
|
183
|
+
const resolve = createPropsResolver( {
|
|
184
|
+
transformers: settingsTransformersRegistry,
|
|
185
|
+
schema: { svg: iconPropType() },
|
|
186
|
+
} );
|
|
187
|
+
const result = await resolve( {
|
|
188
|
+
props: {
|
|
189
|
+
svg: iconPropTypeUtil.create( {
|
|
190
|
+
value: stringPropTypeUtil.create( 'fas fa-star' ),
|
|
191
|
+
library: stringPropTypeUtil.create( '' ),
|
|
192
|
+
} ),
|
|
193
|
+
},
|
|
194
|
+
} );
|
|
195
|
+
|
|
196
|
+
// Assert.
|
|
197
|
+
expect( result.svg ).toEqual( { html: null, url: null } );
|
|
198
|
+
} );
|
|
74
199
|
} );
|
|
@@ -1,122 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FontAwesome7IconDefinition,
|
|
3
|
+
getFontAwesome7IconName,
|
|
4
|
+
resolveFontAwesome7Icon,
|
|
5
|
+
} from '@elementor/editor-controls';
|
|
6
|
+
|
|
1
7
|
import { createTransformer } from '../create-transformer';
|
|
2
8
|
import type { TransformerOptions } from '../types';
|
|
3
9
|
import { processSvgContent } from './process-svg-content';
|
|
4
10
|
|
|
11
|
+
const EMPTY_ICON_RESULT = { html: null, url: null };
|
|
12
|
+
const ICON_SVG_SIZE = '100%';
|
|
13
|
+
const ICON_SVG_OVERFLOW = 'visible';
|
|
14
|
+
|
|
5
15
|
type IconValue = {
|
|
6
16
|
value?: unknown;
|
|
7
17
|
library?: unknown;
|
|
8
18
|
};
|
|
9
19
|
|
|
10
|
-
type FontAwesomeIconJson = [ number, number, unknown, unknown, string ];
|
|
11
|
-
|
|
12
|
-
const FONT_AWESOME_JSON = {
|
|
13
|
-
width: 0,
|
|
14
|
-
height: 1,
|
|
15
|
-
path: 4,
|
|
16
|
-
} as const;
|
|
17
|
-
|
|
18
|
-
const fontAwesomeJsonCache = new Map< string, Record< string, FontAwesomeIconJson > >();
|
|
19
|
-
|
|
20
20
|
export const iconTransformer = createTransformer( async ( value: IconValue, { signal }: TransformerOptions ) => {
|
|
21
21
|
const iconValue = typeof value.value === 'string' ? value.value : null;
|
|
22
22
|
const library = typeof value.library === 'string' ? value.library : null;
|
|
23
23
|
|
|
24
24
|
if ( ! iconValue || ! library ) {
|
|
25
|
-
return
|
|
25
|
+
return EMPTY_ICON_RESULT;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
const iconName =
|
|
29
|
-
const jsonFileName = getFontAwesomeJsonFileName( library );
|
|
28
|
+
const iconName = getFontAwesome7IconName( iconValue );
|
|
30
29
|
|
|
31
|
-
if ( ! iconName
|
|
32
|
-
return
|
|
30
|
+
if ( ! iconName ) {
|
|
31
|
+
return EMPTY_ICON_RESULT;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
const
|
|
36
|
-
const iconData = icons?.[ iconName ];
|
|
34
|
+
const iconData = await resolveFontAwesome7Icon( library, iconName, signal );
|
|
37
35
|
|
|
38
36
|
if ( ! iconData ) {
|
|
39
|
-
return
|
|
37
|
+
return EMPTY_ICON_RESULT;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
const svgText = buildFontAwesomeSvg( iconData );
|
|
43
|
-
const html = processSvgContent( svgText );
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
if ( ! svgText ) {
|
|
43
|
+
return EMPTY_ICON_RESULT;
|
|
44
|
+
}
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
const match = iconValue.match( /^fa\S*\s+fa-(.+)$/ );
|
|
46
|
+
const html = processIconSvgContent( svgText );
|
|
50
47
|
|
|
51
|
-
return
|
|
52
|
-
}
|
|
48
|
+
return { html, url: null };
|
|
49
|
+
} );
|
|
53
50
|
|
|
54
|
-
function
|
|
55
|
-
if (
|
|
51
|
+
function buildFontAwesomeSvg( iconData: FontAwesome7IconDefinition ): string | null {
|
|
52
|
+
if ( iconData.paths.length === 0 ) {
|
|
56
53
|
return null;
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function getAssetsBaseUrl(): string | null {
|
|
63
|
-
const assetsUrl = window.elementorCommon?.config?.urls?.assets;
|
|
56
|
+
const pathMarkup = iconData.paths.map( ( path ) => `<path d="${ escapeSvgPath( path ) }"></path>` ).join( '' );
|
|
64
57
|
|
|
65
|
-
return
|
|
58
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${ iconData.width } ${ iconData.height }">${ pathMarkup }</svg>`;
|
|
66
59
|
}
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
signal?: AbortSignal
|
|
71
|
-
): Promise< Record< string, FontAwesomeIconJson > | null > {
|
|
72
|
-
const cached = fontAwesomeJsonCache.get( jsonFileName );
|
|
73
|
-
|
|
74
|
-
if ( cached ) {
|
|
75
|
-
return cached;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const icons = await loadFontAwesomeIcons( jsonFileName, signal );
|
|
79
|
-
|
|
80
|
-
if ( icons ) {
|
|
81
|
-
fontAwesomeJsonCache.set( jsonFileName, icons );
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return icons;
|
|
61
|
+
function escapeSvgPath( path: string ): string {
|
|
62
|
+
return path.replace( /&/g, '&' ).replace( /"/g, '"' ).replace( /</g, '<' ).replace( />/g, '>' );
|
|
85
63
|
}
|
|
86
64
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
signal?: AbortSignal
|
|
90
|
-
): Promise< Record< string, FontAwesomeIconJson > | null > {
|
|
91
|
-
const assetsUrl = getAssetsBaseUrl();
|
|
65
|
+
function processIconSvgContent( svgText: string ): string | null {
|
|
66
|
+
const html = processSvgContent( svgText );
|
|
92
67
|
|
|
93
|
-
if ( !
|
|
68
|
+
if ( ! html ) {
|
|
94
69
|
return null;
|
|
95
70
|
}
|
|
96
71
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if ( ! response.ok ) {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const data = ( await response.json() ) as { icons?: Record< string, FontAwesomeIconJson > };
|
|
72
|
+
const parser = new DOMParser();
|
|
73
|
+
const doc = parser.parseFromString( html, 'image/svg+xml' );
|
|
74
|
+
const svgElement = doc.querySelector( 'svg' );
|
|
105
75
|
|
|
106
|
-
|
|
107
|
-
} catch {
|
|
76
|
+
if ( ! svgElement ) {
|
|
108
77
|
return null;
|
|
109
78
|
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function buildFontAwesomeSvg( iconData: FontAwesomeIconJson ): string {
|
|
113
|
-
const width = iconData[ FONT_AWESOME_JSON.width ];
|
|
114
|
-
const height = iconData[ FONT_AWESOME_JSON.height ];
|
|
115
|
-
const path = iconData[ FONT_AWESOME_JSON.path ];
|
|
116
79
|
|
|
117
|
-
|
|
118
|
-
|
|
80
|
+
svgElement.setAttribute( 'aria-hidden', 'true' );
|
|
81
|
+
svgElement.style.setProperty( 'width', ICON_SVG_SIZE );
|
|
82
|
+
svgElement.style.setProperty( 'height', ICON_SVG_SIZE );
|
|
83
|
+
svgElement.style.setProperty( 'overflow', ICON_SVG_OVERFLOW );
|
|
119
84
|
|
|
120
|
-
|
|
121
|
-
fontAwesomeJsonCache.clear();
|
|
85
|
+
return svgElement.outerHTML;
|
|
122
86
|
}
|
|
@@ -6,6 +6,13 @@ type Flex = {
|
|
|
6
6
|
flexBasis?: { size: number; unit: string } | string | null;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
const DEFAULT_FLEX_GROW = 0;
|
|
10
|
+
const DEFAULT_FLEX_SHRINK = 1;
|
|
11
|
+
const DEFAULT_FLEX_BASIS = 'auto';
|
|
12
|
+
|
|
13
|
+
const formatBasis = ( basis: NonNullable< Flex[ 'flexBasis' ] > ) =>
|
|
14
|
+
typeof basis === 'object' && basis.size !== undefined ? `${ basis.size }${ basis.unit || '' }` : basis;
|
|
15
|
+
|
|
9
16
|
export const flexTransformer = createTransformer( ( value: Flex ) => {
|
|
10
17
|
const grow = value.flexGrow;
|
|
11
18
|
const shrink = value.flexShrink;
|
|
@@ -19,37 +26,9 @@ export const flexTransformer = createTransformer( ( value: Flex ) => {
|
|
|
19
26
|
return null;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if ( hasGrow && hasShrink && ! hasBasis ) {
|
|
29
|
-
return `${ grow } ${ shrink }`;
|
|
30
|
-
}
|
|
31
|
-
if ( hasGrow && ! hasShrink && hasBasis ) {
|
|
32
|
-
return `${ grow } 1 ${
|
|
33
|
-
typeof basis === 'object' && basis.size !== undefined ? `${ basis.size }${ basis.unit || '' }` : basis
|
|
34
|
-
}`;
|
|
35
|
-
}
|
|
36
|
-
if ( ! hasGrow && hasShrink && hasBasis ) {
|
|
37
|
-
return `0 ${ shrink } ${
|
|
38
|
-
typeof basis === 'object' && basis.size !== undefined ? `${ basis.size }${ basis.unit || '' }` : basis
|
|
39
|
-
}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if ( hasGrow && ! hasShrink && ! hasBasis ) {
|
|
43
|
-
return `${ grow }`;
|
|
44
|
-
}
|
|
45
|
-
if ( ! hasGrow && hasShrink && ! hasBasis ) {
|
|
46
|
-
return `0 ${ shrink }`;
|
|
47
|
-
}
|
|
48
|
-
if ( ! hasGrow && ! hasShrink && hasBasis ) {
|
|
49
|
-
return `0 1 ${
|
|
50
|
-
typeof basis === 'object' && basis.size !== undefined ? `${ basis.size }${ basis.unit || '' }` : basis
|
|
51
|
-
}`;
|
|
52
|
-
}
|
|
29
|
+
const growOut = hasGrow ? grow : DEFAULT_FLEX_GROW;
|
|
30
|
+
const shrinkOut = hasShrink ? shrink : DEFAULT_FLEX_SHRINK;
|
|
31
|
+
const basisOut = hasBasis ? formatBasis( basis ) : DEFAULT_FLEX_BASIS;
|
|
53
32
|
|
|
54
|
-
return
|
|
33
|
+
return `${ growOut } ${ shrinkOut } ${ basisOut }`;
|
|
55
34
|
} );
|