frontblocks-shape-animation-option.js
2 months ago
frontblocks-shape-animation-option.jsx
2 months ago
frontblocks-shape-animations.css
2 months ago
frontblocks-shape-animations.js
9 months ago
frontblocks-shape-animation-option.js
383 lines
| 1 | "use strict"; |
| 2 | |
| 3 | function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } |
| 4 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } |
| 5 | function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } |
| 6 | function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } |
| 7 | function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } |
| 8 | function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } |
| 9 | (function () { |
| 10 | 'use strict'; |
| 11 | |
| 12 | var __ = wp.i18n.__; |
| 13 | var addFilter = wp.hooks.addFilter; |
| 14 | var _wp$element = wp.element, |
| 15 | Fragment = _wp$element.Fragment, |
| 16 | useState = _wp$element.useState; |
| 17 | var InspectorControls = wp.blockEditor.InspectorControls; |
| 18 | var _wp$components = wp.components, |
| 19 | PanelBody = _wp$components.PanelBody, |
| 20 | ToggleControl = _wp$components.ToggleControl, |
| 21 | TextareaControl = _wp$components.TextareaControl, |
| 22 | Button = _wp$components.Button, |
| 23 | Notice = _wp$components.Notice; |
| 24 | |
| 25 | /** |
| 26 | * Add custom SVG animation controls to GenerateBlocks Shape block. |
| 27 | */ |
| 28 | function addShapeAnimationControls(BlockEdit) { |
| 29 | return function (props) { |
| 30 | // Only add controls to shape and icon blocks. |
| 31 | if (props.name !== 'generateblocks/shape' && props.name !== 'core/icon') { |
| 32 | return wp.element.createElement(BlockEdit, props); |
| 33 | } |
| 34 | var attributes = props.attributes, |
| 35 | setAttributes = props.setAttributes; |
| 36 | var _attributes$frblCusto = attributes.frblCustomSvgAnimationEnabled, |
| 37 | frblCustomSvgAnimationEnabled = _attributes$frblCusto === void 0 ? false : _attributes$frblCusto, |
| 38 | _attributes$frblCusto2 = attributes.frblCustomSvgAnimationJson, |
| 39 | frblCustomSvgAnimationJson = _attributes$frblCusto2 === void 0 ? '' : _attributes$frblCusto2; |
| 40 | var _useState = useState(''), |
| 41 | _useState2 = _slicedToArray(_useState, 2), |
| 42 | jsonError = _useState2[0], |
| 43 | setJsonError = _useState2[1]; |
| 44 | var _useState3 = useState(null), |
| 45 | _useState4 = _slicedToArray(_useState3, 2), |
| 46 | jsonPreview = _useState4[0], |
| 47 | setJsonPreview = _useState4[1]; |
| 48 | |
| 49 | // Detect if JSON is Lottie format. |
| 50 | var isLottieJson = function isLottieJson(parsed) { |
| 51 | return parsed.v && parsed.fr && parsed.layers; |
| 52 | }; |
| 53 | |
| 54 | // Validate JSON. |
| 55 | var validateJson = function validateJson(jsonString) { |
| 56 | try { |
| 57 | var parsed = JSON.parse(jsonString); |
| 58 | |
| 59 | // Check if it's Lottie format. |
| 60 | if (isLottieJson(parsed)) { |
| 61 | setJsonError(''); |
| 62 | setJsonPreview({ |
| 63 | type: 'lottie', |
| 64 | name: parsed.nm || 'Lottie Animation', |
| 65 | version: parsed.v, |
| 66 | framerate: parsed.fr, |
| 67 | duration: parsed.op ? (parsed.op / parsed.fr).toFixed(2) + 's' : 'N/A' |
| 68 | }); |
| 69 | return true; |
| 70 | } |
| 71 | |
| 72 | // Check if it's custom SVG + CSS format. |
| 73 | if (!parsed.svg) { |
| 74 | setJsonError(__('Missing "svg" property in JSON (or not valid Lottie format)', 'frontblocks')); |
| 75 | return false; |
| 76 | } |
| 77 | if (!parsed.animation || !parsed.animation.keyframes || !parsed.animation.name) { |
| 78 | setJsonError(__('Missing required animation properties (keyframes, name)', 'frontblocks')); |
| 79 | return false; |
| 80 | } |
| 81 | setJsonError(''); |
| 82 | setJsonPreview({ |
| 83 | type: 'css', |
| 84 | name: parsed.animation.name, |
| 85 | trigger: parsed.animation.trigger || 'load', |
| 86 | duration: parsed.animation.duration || '1s' |
| 87 | }); |
| 88 | return true; |
| 89 | } catch (e) { |
| 90 | setJsonError(__('Invalid JSON format: ', 'frontblocks') + e.message); |
| 91 | return false; |
| 92 | } |
| 93 | }; |
| 94 | |
| 95 | // Handle JSON change. |
| 96 | var handleJsonChange = function handleJsonChange(value) { |
| 97 | setAttributes({ |
| 98 | frblCustomSvgAnimationJson: value |
| 99 | }); |
| 100 | if (value.trim()) { |
| 101 | validateJson(value); |
| 102 | } else { |
| 103 | setJsonError(''); |
| 104 | setJsonPreview(null); |
| 105 | } |
| 106 | }; |
| 107 | |
| 108 | // Example JSON template. |
| 109 | var exampleJson = JSON.stringify({ |
| 110 | "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"48\" height=\"48\" fill=\"currentColor\"><path d=\"M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z\"/></svg>", |
| 111 | "animation": { |
| 112 | "name": "myCustomAnimation", |
| 113 | "keyframes": "@keyframes myCustomAnimation { 0% { transform: rotate(0deg) scale(1); } 50% { transform: rotate(180deg) scale(1.2); } 100% { transform: rotate(360deg) scale(1); } }", |
| 114 | "duration": "2s", |
| 115 | "delay": "0s", |
| 116 | "infinite": true, |
| 117 | "trigger": "load" |
| 118 | } |
| 119 | }, null, 2); |
| 120 | return wp.element.createElement(Fragment, {}, wp.element.createElement(BlockEdit, props), wp.element.createElement(InspectorControls, {}, wp.element.createElement(PanelBody, { |
| 121 | title: __('FrontBlocks Custom SVG Animation', 'frontblocks'), |
| 122 | initialOpen: false |
| 123 | }, wp.element.createElement(ToggleControl, { |
| 124 | label: __('Enable Custom SVG Animation', 'frontblocks'), |
| 125 | checked: frblCustomSvgAnimationEnabled, |
| 126 | onChange: function onChange(value) { |
| 127 | setAttributes({ |
| 128 | frblCustomSvgAnimationEnabled: value |
| 129 | }); |
| 130 | }, |
| 131 | help: __('Replace SVG and add custom CSS animations via JSON', 'frontblocks') |
| 132 | }), frblCustomSvgAnimationEnabled && wp.element.createElement(Fragment, {}, wp.element.createElement('p', { |
| 133 | style: { |
| 134 | fontSize: '12px', |
| 135 | marginBottom: '8px', |
| 136 | color: '#757575' |
| 137 | } |
| 138 | }, __('Paste your JSON configuration below:', 'frontblocks')), wp.element.createElement(TextareaControl, { |
| 139 | label: __('JSON Configuration', 'frontblocks'), |
| 140 | value: frblCustomSvgAnimationJson, |
| 141 | onChange: handleJsonChange, |
| 142 | rows: 10, |
| 143 | help: __('JSON with svg and animation properties', 'frontblocks') |
| 144 | }), jsonError && wp.element.createElement(Notice, { |
| 145 | status: 'error', |
| 146 | isDismissible: false |
| 147 | }, jsonError), jsonPreview && wp.element.createElement(Notice, { |
| 148 | status: 'success', |
| 149 | isDismissible: false |
| 150 | }, __('✓ Valid JSON configuration', 'frontblocks')), wp.element.createElement('details', { |
| 151 | style: { |
| 152 | marginTop: '16px', |
| 153 | fontSize: '12px' |
| 154 | } |
| 155 | }, wp.element.createElement('summary', { |
| 156 | style: { |
| 157 | cursor: 'pointer', |
| 158 | fontWeight: '600', |
| 159 | marginBottom: '8px' |
| 160 | } |
| 161 | }, __('📋 Show example JSON', 'frontblocks')), wp.element.createElement('pre', { |
| 162 | style: { |
| 163 | background: '#f6f7f7', |
| 164 | padding: '12px', |
| 165 | borderRadius: '4px', |
| 166 | overflow: 'auto', |
| 167 | fontSize: '11px', |
| 168 | maxHeight: '300px' |
| 169 | } |
| 170 | }, exampleJson), wp.element.createElement(Button, { |
| 171 | isSecondary: true, |
| 172 | isSmall: true, |
| 173 | onClick: function onClick() { |
| 174 | setAttributes({ |
| 175 | frblCustomSvgAnimationJson: exampleJson |
| 176 | }); |
| 177 | validateJson(exampleJson); |
| 178 | }, |
| 179 | style: { |
| 180 | marginTop: '8px' |
| 181 | } |
| 182 | }, __('Use this example', 'frontblocks'))), jsonPreview && wp.element.createElement('div', { |
| 183 | style: { |
| 184 | marginTop: '16px', |
| 185 | padding: '12px', |
| 186 | background: jsonPreview.type === 'lottie' ? '#e8f5e9' : '#f6f7f7', |
| 187 | borderRadius: '4px', |
| 188 | fontSize: '12px' |
| 189 | } |
| 190 | }, wp.element.createElement('strong', {}, jsonPreview.type === 'lottie' ? '🎬 Lottie Animation' : '🎨 CSS Animation'), wp.element.createElement('br'), wp.element.createElement('span', {}, __('Name:', 'frontblocks') + ' ' + (jsonPreview.name || 'N/A')), wp.element.createElement('br'), jsonPreview.type === 'lottie' ? wp.element.createElement('span', {}, __('Version:', 'frontblocks') + ' ' + jsonPreview.version + ', ' + __('FPS:', 'frontblocks') + ' ' + jsonPreview.framerate) : wp.element.createElement('span', {}, __('Trigger:', 'frontblocks') + ' ' + jsonPreview.trigger), wp.element.createElement('br'), wp.element.createElement('span', {}, __('Duration:', 'frontblocks') + ' ' + jsonPreview.duration)))))); |
| 191 | }; |
| 192 | } |
| 193 | addFilter('editor.BlockEdit', 'frontblocks/shape-custom-svg-animation-controls', addShapeAnimationControls); |
| 194 | |
| 195 | /** |
| 196 | * Add preview and handle size/color inheritance in editor. |
| 197 | */ |
| 198 | var createHigherOrderComponent = wp.compose.createHigherOrderComponent; |
| 199 | var _wp$element2 = wp.element, |
| 200 | useEffect = _wp$element2.useEffect, |
| 201 | useRef = _wp$element2.useRef; |
| 202 | var withShapeAnimationPreview = createHigherOrderComponent(function (BlockListBlock) { |
| 203 | return function (props) { |
| 204 | if (props.name !== 'generateblocks/shape' && props.name !== 'core/icon') { |
| 205 | return wp.element.createElement(BlockListBlock, props); |
| 206 | } |
| 207 | var attributes = props.attributes; |
| 208 | var isIconBlock = props.name === 'core/icon'; |
| 209 | var _attributes$frblCusto3 = attributes.frblCustomSvgAnimationEnabled, |
| 210 | frblCustomSvgAnimationEnabled = _attributes$frblCusto3 === void 0 ? false : _attributes$frblCusto3, |
| 211 | _attributes$frblCusto4 = attributes.frblCustomSvgAnimationJson, |
| 212 | frblCustomSvgAnimationJson = _attributes$frblCusto4 === void 0 ? '' : _attributes$frblCusto4, |
| 213 | _attributes$styles = attributes.styles, |
| 214 | styles = _attributes$styles === void 0 ? {} : _attributes$styles; |
| 215 | var lottieContainerRef = useRef(null); |
| 216 | var lottieInstanceRef = useRef(null); |
| 217 | useEffect(function () { |
| 218 | if (!frblCustomSvgAnimationEnabled || !frblCustomSvgAnimationJson) { |
| 219 | return; |
| 220 | } |
| 221 | try { |
| 222 | var parsed = JSON.parse(frblCustomSvgAnimationJson); |
| 223 | |
| 224 | // Check if it's Lottie. |
| 225 | var isLottie = parsed.v && parsed.fr && parsed.layers; |
| 226 | if (isLottie && typeof lottie !== 'undefined') { |
| 227 | // Wait for DOM to be ready. |
| 228 | setTimeout(function () { |
| 229 | var blockElement = document.querySelector('[data-block="' + props.clientId + '"]'); |
| 230 | if (!blockElement) return; |
| 231 | |
| 232 | // Find or create Lottie container in the block. |
| 233 | var lottieContainer = blockElement.querySelector('.frbl-lottie-preview'); |
| 234 | if (!lottieContainer) { |
| 235 | // Find the inner element: .gb-shape for generateblocks/shape, .wp-block-icon for core/icon. |
| 236 | var containerSelector = isIconBlock ? '.wp-block-icon' : '.gb-shape'; |
| 237 | var shapeElement = blockElement.querySelector(containerSelector); |
| 238 | if (shapeElement) { |
| 239 | // HIDE original SVG completely. |
| 240 | var originalSvg = shapeElement.querySelector('svg'); |
| 241 | if (originalSvg) { |
| 242 | originalSvg.style.display = 'none'; |
| 243 | } |
| 244 | |
| 245 | // Create Lottie preview container. |
| 246 | lottieContainer = document.createElement('div'); |
| 247 | lottieContainer.className = 'frbl-lottie-preview'; |
| 248 | lottieContainer.style.cssText = 'position: relative; width: 100%; height: 100%; pointer-events: none;'; |
| 249 | |
| 250 | // Replace content with Lottie container. |
| 251 | shapeElement.appendChild(lottieContainer); |
| 252 | } |
| 253 | } |
| 254 | if (lottieContainer) { |
| 255 | // Clean up previous instance. |
| 256 | if (lottieInstanceRef.current) { |
| 257 | lottieInstanceRef.current.destroy(); |
| 258 | } |
| 259 | lottieContainer.innerHTML = ''; |
| 260 | |
| 261 | // Apply size from Shape block styles. |
| 262 | var svgStyles = styles.svg || {}; |
| 263 | if (svgStyles.width) { |
| 264 | lottieContainer.style.width = svgStyles.width; |
| 265 | } |
| 266 | if (svgStyles.height) { |
| 267 | lottieContainer.style.height = svgStyles.height; |
| 268 | } |
| 269 | |
| 270 | // Set color BEFORE creating animation. |
| 271 | if (svgStyles.fill) { |
| 272 | lottieContainer.style.setProperty('--lottie-color', svgStyles.fill); |
| 273 | } |
| 274 | |
| 275 | // Create Lottie animation. |
| 276 | var loop = parsed.animation && typeof parsed.animation.loop !== 'undefined' ? parsed.animation.loop : true; |
| 277 | var autoplay = parsed.animation && typeof parsed.animation.autoplay !== 'undefined' ? parsed.animation.autoplay : true; |
| 278 | var speed = parsed.animation && parsed.animation.speed ? parsed.animation.speed : 1; |
| 279 | var animation = lottie.loadAnimation({ |
| 280 | container: lottieContainer, |
| 281 | renderer: 'svg', |
| 282 | loop: loop, |
| 283 | autoplay: autoplay, |
| 284 | animationData: parsed |
| 285 | }); |
| 286 | animation.setSpeed(speed); |
| 287 | lottieInstanceRef.current = animation; |
| 288 | |
| 289 | // Apply color IMMEDIATELY after creating animation. |
| 290 | applyColorToLottie(lottieContainer, svgStyles.fill); |
| 291 | |
| 292 | // Apply color immediately after SVG is added to DOM. |
| 293 | animation.addEventListener('DOMLoaded', function () { |
| 294 | applyColorToLottie(lottieContainer, svgStyles.fill); |
| 295 | }); |
| 296 | |
| 297 | // Apply color on EVERY SINGLE FRAME (most aggressive approach). |
| 298 | animation.addEventListener('enterFrame', function () { |
| 299 | applyColorToLottie(lottieContainer, svgStyles.fill); |
| 300 | }); |
| 301 | |
| 302 | // Apply color multiple times immediately for instant coverage. |
| 303 | setTimeout(function () { |
| 304 | applyColorToLottie(lottieContainer, svgStyles.fill); |
| 305 | }, 0); |
| 306 | setTimeout(function () { |
| 307 | applyColorToLottie(lottieContainer, svgStyles.fill); |
| 308 | }, 10); |
| 309 | setTimeout(function () { |
| 310 | applyColorToLottie(lottieContainer, svgStyles.fill); |
| 311 | }, 20); |
| 312 | setTimeout(function () { |
| 313 | applyColorToLottie(lottieContainer, svgStyles.fill); |
| 314 | }, 50); |
| 315 | setTimeout(function () { |
| 316 | applyColorToLottie(lottieContainer, svgStyles.fill); |
| 317 | }, 100); |
| 318 | } |
| 319 | }, 300); |
| 320 | } |
| 321 | } catch (error) { |
| 322 | console.warn('FrontBlocks: Error rendering Lottie preview', error); |
| 323 | } |
| 324 | |
| 325 | // Cleanup on unmount. |
| 326 | return function () { |
| 327 | if (lottieInstanceRef.current) { |
| 328 | lottieInstanceRef.current.destroy(); |
| 329 | lottieInstanceRef.current = null; |
| 330 | } |
| 331 | }; |
| 332 | }, [frblCustomSvgAnimationEnabled, frblCustomSvgAnimationJson, props.clientId, styles, isIconBlock]); |
| 333 | return wp.element.createElement(BlockListBlock, props); |
| 334 | }; |
| 335 | }, 'withShapeAnimationPreview'); |
| 336 | |
| 337 | /** |
| 338 | * Apply color to Lottie animation SVG elements - AGGRESSIVELY. |
| 339 | */ |
| 340 | function applyColorToLottie(container, color) { |
| 341 | if (!container) return; |
| 342 | |
| 343 | // If no color provided, return early. |
| 344 | if (!color) return; |
| 345 | |
| 346 | // Set CSS variable. |
| 347 | container.style.setProperty('--lottie-color', color); |
| 348 | |
| 349 | // Find ALL SVG elements - be very aggressive. |
| 350 | var elements = container.querySelectorAll('svg *, [fill], [stroke]'); |
| 351 | elements.forEach(function (element) { |
| 352 | // Get current fill and stroke. |
| 353 | var currentFill = element.getAttribute('fill'); |
| 354 | var currentStroke = element.getAttribute('stroke'); |
| 355 | var computedFill = window.getComputedStyle(element).fill; |
| 356 | |
| 357 | // Apply to anything that has color. |
| 358 | if (currentFill && currentFill !== 'none' && currentFill !== 'transparent') { |
| 359 | element.setAttribute('fill', color); |
| 360 | element.style.setProperty('fill', color, 'important'); |
| 361 | } |
| 362 | |
| 363 | // Also check computed style. |
| 364 | if (computedFill && computedFill !== 'none' && computedFill !== 'transparent' && computedFill !== 'rgba(0, 0, 0, 0)') { |
| 365 | element.style.setProperty('fill', color, 'important'); |
| 366 | } |
| 367 | |
| 368 | // Also handle stroke if it exists. |
| 369 | if (currentStroke && currentStroke !== 'none' && currentStroke !== 'transparent') { |
| 370 | element.setAttribute('stroke', color); |
| 371 | element.style.setProperty('stroke', color, 'important'); |
| 372 | } |
| 373 | }); |
| 374 | |
| 375 | // Also try to set fill on the SVG itself. |
| 376 | var svg = container.querySelector('svg'); |
| 377 | if (svg) { |
| 378 | svg.style.setProperty('fill', color, 'important'); |
| 379 | } |
| 380 | } |
| 381 | addFilter('editor.BlockListBlock', 'frontblocks/shape-animation-preview', withShapeAnimationPreview); |
| 382 | })(); |
| 383 |