PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.14.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.14.0
2.14.0 2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 All 81 releases
ablocks / includes / classes / atomic-block-base.php

atomic-block-base.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.14.0, at includes/classes/atomic-block-base.php

315 lines 11.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ABlocks\Classes;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 use ABlocks\Classes\BlockBaseAbstract;
9 use ABlocks\Classes\AtomicStyles;
10 use ABlocks\Controls\Alignment;
11 use ABlocks\Helper;
12
13 /**
14 * Shared base for every atomic block (text, image, svg, div, flex, grid).
15 *
16 * Renders the block's per-state (Normal / Hover / Focus / Active) × per-device
17 * styles onto a single higher-specificity selector — `.ablocks-block-{id}
18 * .ablocks-{block_name}` (0-2-0) so the block's own styles beat any applied
19 * global class (0-1-0) — plus interaction animations and an optional
20 * `extra_css()` hook for block-specific declarations. Concrete blocks only set
21 * `$block_name` and (optionally) override `extra_css()` / `block_css_class()`.
22 */
23 abstract class AtomicBlockBase extends BlockBaseAbstract {
24
25 // Elementor easing names -> CSS timing functions.
26 protected static $easing_css = [
27 'easeIn' => 'cubic-bezier(0.42, 0, 1, 1)',
28 'easeOut' => 'cubic-bezier(0, 0, 0.58, 1)',
29 'easeInOut' => 'cubic-bezier(0.42, 0, 0.58, 1)',
30 'backIn' => 'cubic-bezier(0.36, 0, 0.66, -0.56)',
31 'backOut' => 'cubic-bezier(0.34, 1.56, 0.64, 1)',
32 'backInOut' => 'cubic-bezier(0.68, -0.6, 0.32, 1.6)',
33 'linear' => 'linear',
34 ];
35
36 /** The block's own class, e.g. `ablocks-atomic-div`. */
37 protected function block_css_class() {
38 return 'ablocks-' . $this->block_name;
39 }
40
41 /** Whether to emit a per-device text-align from the `alignment` attribute. */
42 protected function supports_alignment() {
43 return true;
44 }
45
46 /**
47 * Keep saved markup pointing at the style class build_css() emits — see
48 * AtomicStyles::refresh_style_class().
49 */
50 public function render_callback( $attributes, $content, $block_instance ) {
51 $content = AtomicStyles::refresh_style_class(
52 $content,
53 isset( $attributes['styles'] ) && is_array( $attributes['styles'] ) ? $attributes['styles'] : [],
54 $this->supports_alignment() && isset( $attributes['alignment'] ) && is_array( $attributes['alignment'] ) ? $attributes['alignment'] : []
55 );
56 return parent::render_callback( $attributes, $content, $block_instance );
57 }
58
59 /** Block-specific declarations, appended after the shared style rules. */
60 protected function extra_css( $base, $attributes ) {
61 return '';
62 }
63
64 public function build_css( $attributes ) {
65 $block_id = isset( $attributes['block_id'] ) ? $attributes['block_id'] : '';
66 if ( '' === $block_id ) {
67 return '';
68 }
69
70 $base = '.ablocks-block-' . $block_id . '.' . $this->block_css_class();
71 $styles = isset( $attributes['styles'] ) && is_array( $attributes['styles'] ) ? $attributes['styles'] : [];
72 $alignment = isset( $attributes['alignment'] ) && is_array( $attributes['alignment'] ) ? $attributes['alignment'] : [];
73 $css = '';
74
75 /*
76 * The block's state/breakpoint styles are emitted as a CONTENT-HASHED
77 * SHARED class, not per instance: every block whose styles compile to
78 * the same rule set gets the same `ablocks-s-{hash}` class, and the rule
79 * is written once per request no matter how many blocks carry it. The
80 * editor stamps the identical class into the markup at save time.
81 *
82 * Alignment folds into the same rule set (see compile_rules) rather than
83 * being emitted separately — it is device-suffixed and belongs to the
84 * normal state, and keeping it in the hash is what stops two blocks with
85 * identical styles but different alignment from sharing a class.
86 */
87 $registered = AtomicStyles::register_styles(
88 $styles,
89 $this->supports_alignment() ? $alignment : []
90 );
91 $css .= $registered['css'];
92
93 // Transition (smooths hover / focus / active state changes).
94 if ( ! empty( $attributes['transitionDuration'] ) ) {
95 $css .= $base . '{transition:all ' . $attributes['transitionDuration'] . 's ease;}';
96 }
97
98 // Block-specific extra CSS.
99 $css .= $this->extra_css( $base, $attributes );
100
101 // Interaction animations (multiple, per trigger + per breakpoint).
102 $animations = isset( $attributes['animations'] ) && is_array( $attributes['animations'] ) ? $attributes['animations'] : [];
103 if ( empty( $animations ) ) {
104 $animations = $this->migrate_legacy_animation( $attributes );
105 }
106 $css .= $this->build_animation_css( $base, $animations, Helper::get_responsive_devices() );
107
108 // Shared Advanced tab: Custom CSS + responsive visibility.
109 $css .= AtomicStyles::advanced_css( $base, $attributes );
110
111 return $css;
112 }
113
114 /** Back-compat: convert the old single `animation` object to the new list. */
115 protected function migrate_legacy_animation( $attributes ) {
116 $anim = isset( $attributes['animation'] ) && is_array( $attributes['animation'] ) ? $attributes['animation'] : [];
117 if ( empty( $anim['type'] ) ) {
118 return [];
119 }
120 $map = [
121 'fadeIn' => [ 'fade', '' ],
122 'slideUp' => [ 'slide', 'bottom' ],
123 'slideDown' => [ 'slide', 'top' ],
124 'slideLeft' => [ 'slide', 'right' ],
125 'slideRight' => [ 'slide', 'left' ],
126 'zoomIn' => [ 'scale', '' ],
127 ];
128 $m = isset( $map[ $anim['type'] ] ) ? $map[ $anim['type'] ] : [ 'fade', '' ];
129 return [
130 [
131 'id' => 'legacy',
132 'trigger' => 'load',
133 'effect' => $m[0],
134 'type' => 'in',
135 'direction' => $m[1],
136 'duration' => (float) ( ! empty( $anim['duration'] ) ? $anim['duration'] : 0.6 ) * 1000,
137 'delay' => (float) ( ! empty( $anim['delay'] ) ? $anim['delay'] : 0 ) * 1000,
138 'easing' => 'easeIn',
139 'repeat' => '',
140 'times' => 1,
141 'off' => [],
142 ],
143 ];
144 }
145
146 protected function keyframe_name( $a ) {
147 $effect = ! empty( $a['effect'] ) ? $a['effect'] : 'fade';
148 $type = ( isset( $a['type'] ) && 'out' === $a['type'] ) ? 'out' : 'in';
149 $dir = ! empty( $a['direction'] ) ? $a['direction'] : 'none';
150 if ( 'slide' === $effect && empty( $a['direction'] ) ) {
151 $dir = 'top';
152 }
153 return 'ablocks-a-' . $effect . '-' . $type . '-' . $dir;
154 }
155
156 protected function offset_transform( $direction, $dist ) {
157 $n = -$dist;
158 $p = $dist;
159 switch ( $direction ) {
160 case 'top': return 'translateY(' . $n . 'px)';
161 case 'bottom': return 'translateY(' . $p . 'px)';
162 case 'left': return 'translateX(' . $n . 'px)';
163 case 'right': return 'translateX(' . $p . 'px)';
164 case 'top-left': return 'translate(' . $n . 'px, ' . $n . 'px)';
165 case 'top-right': return 'translate(' . $p . 'px, ' . $n . 'px)';
166 case 'bottom-left': return 'translate(' . $n . 'px, ' . $p . 'px)';
167 case 'bottom-right': return 'translate(' . $p . 'px, ' . $p . 'px)';
168 default: return '';
169 }
170 }
171
172 protected function keyframe_css( $a ) {
173 $effect = ! empty( $a['effect'] ) ? $a['effect'] : 'fade';
174 $type = ( isset( $a['type'] ) && 'out' === $a['type'] ) ? 'out' : 'in';
175 $direction = isset( $a['direction'] ) ? $a['direction'] : '';
176 if ( 'slide' === $effect && '' === $direction ) {
177 $direction = 'top';
178 }
179 $dist = ( 'slide' === $effect ) ? 60 : 24;
180 $translate = $this->offset_transform( $direction, $dist );
181 $scale = ( 'scale' === $effect ) ? 'scale(0.85)' : '';
182 $off = trim( $translate . ' ' . $scale );
183 if ( '' === $off ) {
184 $off = 'none';
185 }
186 $name = $this->keyframe_name( $a );
187 if ( 'out' === $type ) {
188 return '@keyframes ' . $name . '{from{opacity:1;transform:none;}to{opacity:0;transform:' . $off . ';}}';
189 }
190 return '@keyframes ' . $name . '{from{opacity:0;transform:' . $off . ';}to{opacity:1;transform:none;}}';
191 }
192
193 protected function iteration_count( $a ) {
194 if ( isset( $a['repeat'] ) && 'loop' === $a['repeat'] ) {
195 return 'infinite';
196 }
197 if ( isset( $a['repeat'] ) && 'times' === $a['repeat'] ) {
198 $n = isset( $a['times'] ) ? (int) $a['times'] : 1;
199 return $n > 0 ? (string) $n : '1';
200 }
201 return '1';
202 }
203
204 protected function anim_shorthand( $a ) {
205 $dur = ( isset( $a['duration'] ) && '' !== $a['duration'] ) ? $a['duration'] : 600;
206 $del = ( isset( $a['delay'] ) && '' !== $a['delay'] ) ? $a['delay'] : 0;
207 $easing = ( isset( $a['easing'] ) && isset( self::$easing_css[ $a['easing'] ] ) ) ? self::$easing_css[ $a['easing'] ] : self::$easing_css['easeIn'];
208 return $this->keyframe_name( $a ) . ' ' . $dur . 'ms ' . $easing . ' ' . $del . 'ms ' . $this->iteration_count( $a ) . ' both';
209 }
210
211 protected function trigger_suffix( $trigger ) {
212 switch ( $trigger ) {
213 case 'hover': return ':hover';
214 case 'scrollIn': return '.ablocks-in-view';
215 case 'click': return '.ablocks-anim-active';
216 default: return '';
217 }
218 }
219
220 protected function build_animation_css( $base, $animations, $devices ) {
221 if ( empty( $animations ) || ! is_array( $animations ) ) {
222 return '';
223 }
224 $css = '';
225
226 $seen = [];
227 foreach ( $animations as $a ) {
228 $name = $this->keyframe_name( $a );
229 if ( ! isset( $seen[ $name ] ) ) {
230 $seen[ $name ] = true;
231 $css .= $this->keyframe_css( $a );
232 }
233 }
234
235 $has_scroll_in = false;
236 foreach ( $animations as $a ) {
237 if ( ( isset( $a['trigger'] ) ? $a['trigger'] : '' ) === 'scrollIn' && ( ! isset( $a['type'] ) || 'out' !== $a['type'] ) ) {
238 $has_scroll_in = true;
239 break;
240 }
241 }
242 if ( $has_scroll_in ) {
243 $css .= $base . '.ablocks-anim-scroll.ablocks-anim-ready:not(.ablocks-in-view){opacity:0;}';
244 }
245
246 foreach ( [ 'load', 'scrollIn', 'hover', 'click' ] as $trigger ) {
247 $group = array_filter( $animations, function ( $a ) use ( $trigger ) {
248 return ( isset( $a['trigger'] ) ? $a['trigger'] : 'load' ) === $trigger;
249 } );
250 if ( empty( $group ) ) {
251 continue;
252 }
253 $sel = $base . $this->trigger_suffix( $trigger );
254 $shorthand_for = function ( $device_id ) use ( $group ) {
255 $parts = [];
256 foreach ( $group as $a ) {
257 if ( empty( $a['off'][ $device_id ] ) ) {
258 $parts[] = $this->anim_shorthand( $a );
259 }
260 }
261 return implode( ', ', $parts );
262 };
263 $desktop = $shorthand_for( 'Desktop' );
264 if ( '' !== $desktop ) {
265 $css .= $sel . '{animation:' . $desktop . ';}';
266 }
267 foreach ( $devices as $d ) {
268 if ( 'Desktop' === $d['id'] || ( empty( $d['max'] ) && empty( $d['min'] ) ) ) {
269 continue;
270 }
271 $sh = $shorthand_for( $d['id'] );
272 if ( $sh !== $desktop ) {
273 $media = Helper::breakpoint_media_condition( isset( $d['min'] ) ? $d['min'] : 0, isset( $d['max'] ) ? $d['max'] : 0 );
274 if ( '' !== $media ) {
275 $css .= '@media screen and ' . $media . '{' . $sel . '{animation:' . ( '' !== $sh ? $sh : 'none' ) . ';}}';
276 }
277 }
278 }
279 }
280
281 $scroll_on = array_filter( $animations, function ( $a ) {
282 return ( isset( $a['trigger'] ) ? $a['trigger'] : '' ) === 'scrollOn';
283 } );
284 if ( ! empty( $scroll_on ) ) {
285 $scrub = function ( $device_id ) use ( $scroll_on ) {
286 $parts = [];
287 foreach ( $scroll_on as $a ) {
288 if ( empty( $a['off'][ $device_id ] ) ) {
289 $parts[] = $this->keyframe_name( $a ) . ' 1000ms linear 0ms 1 both';
290 }
291 }
292 return implode( ', ', $parts );
293 };
294 $desk = $scrub( 'Desktop' );
295 if ( '' !== $desk ) {
296 $css .= $base . '{animation:' . $desk . ';animation-play-state:paused;}';
297 }
298 foreach ( $devices as $d ) {
299 if ( 'Desktop' === $d['id'] || ( empty( $d['max'] ) && empty( $d['min'] ) ) ) {
300 continue;
301 }
302 $sh = $scrub( $d['id'] );
303 if ( $sh !== $desk ) {
304 $media = Helper::breakpoint_media_condition( isset( $d['min'] ) ? $d['min'] : 0, isset( $d['max'] ) ? $d['max'] : 0 );
305 if ( '' !== $media ) {
306 $css .= '@media screen and ' . $media . '{' . $base . '{animation:' . ( '' !== $sh ? $sh : 'none' ) . ';}}';
307 }
308 }
309 }
310 }
311
312 return $css;
313 }
314 }
315