PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.13.1
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.13.1
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 1.1.2 All 80 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.13.1, at includes/classes/atomic-block-base.php

302 lines 10.7 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 /** Block-specific declarations, appended after the shared style rules. */
47 protected function extra_css( $base, $attributes ) {
48 return '';
49 }
50
51 public function build_css( $attributes ) {
52 $block_id = isset( $attributes['block_id'] ) ? $attributes['block_id'] : '';
53 if ( '' === $block_id ) {
54 return '';
55 }
56
57 $base = '.ablocks-block-' . $block_id . '.' . $this->block_css_class();
58 $styles = isset( $attributes['styles'] ) && is_array( $attributes['styles'] ) ? $attributes['styles'] : [];
59 $alignment = isset( $attributes['alignment'] ) && is_array( $attributes['alignment'] ) ? $attributes['alignment'] : [];
60 $css = '';
61
62 /*
63 * The block's state/breakpoint styles are emitted as a CONTENT-HASHED
64 * SHARED class, not per instance: every block whose styles compile to
65 * the same rule set gets the same `ablocks-s-{hash}` class, and the rule
66 * is written once per request no matter how many blocks carry it. The
67 * editor stamps the identical class into the markup at save time.
68 *
69 * Alignment folds into the same rule set (see compile_rules) rather than
70 * being emitted separately — it is device-suffixed and belongs to the
71 * normal state, and keeping it in the hash is what stops two blocks with
72 * identical styles but different alignment from sharing a class.
73 */
74 $registered = AtomicStyles::register_styles(
75 $styles,
76 $this->supports_alignment() ? $alignment : []
77 );
78 $css .= $registered['css'];
79
80 // Transition (smooths hover / focus / active state changes).
81 if ( ! empty( $attributes['transitionDuration'] ) ) {
82 $css .= $base . '{transition:all ' . $attributes['transitionDuration'] . 's ease;}';
83 }
84
85 // Block-specific extra CSS.
86 $css .= $this->extra_css( $base, $attributes );
87
88 // Interaction animations (multiple, per trigger + per breakpoint).
89 $animations = isset( $attributes['animations'] ) && is_array( $attributes['animations'] ) ? $attributes['animations'] : [];
90 if ( empty( $animations ) ) {
91 $animations = $this->migrate_legacy_animation( $attributes );
92 }
93 $css .= $this->build_animation_css( $base, $animations, Helper::get_responsive_devices() );
94
95 // Shared Advanced tab: Custom CSS + responsive visibility.
96 $css .= AtomicStyles::advanced_css( $base, $attributes );
97
98 return $css;
99 }
100
101 /** Back-compat: convert the old single `animation` object to the new list. */
102 protected function migrate_legacy_animation( $attributes ) {
103 $anim = isset( $attributes['animation'] ) && is_array( $attributes['animation'] ) ? $attributes['animation'] : [];
104 if ( empty( $anim['type'] ) ) {
105 return [];
106 }
107 $map = [
108 'fadeIn' => [ 'fade', '' ],
109 'slideUp' => [ 'slide', 'bottom' ],
110 'slideDown' => [ 'slide', 'top' ],
111 'slideLeft' => [ 'slide', 'right' ],
112 'slideRight' => [ 'slide', 'left' ],
113 'zoomIn' => [ 'scale', '' ],
114 ];
115 $m = isset( $map[ $anim['type'] ] ) ? $map[ $anim['type'] ] : [ 'fade', '' ];
116 return [
117 [
118 'id' => 'legacy',
119 'trigger' => 'load',
120 'effect' => $m[0],
121 'type' => 'in',
122 'direction' => $m[1],
123 'duration' => (float) ( ! empty( $anim['duration'] ) ? $anim['duration'] : 0.6 ) * 1000,
124 'delay' => (float) ( ! empty( $anim['delay'] ) ? $anim['delay'] : 0 ) * 1000,
125 'easing' => 'easeIn',
126 'repeat' => '',
127 'times' => 1,
128 'off' => [],
129 ],
130 ];
131 }
132
133 protected function keyframe_name( $a ) {
134 $effect = ! empty( $a['effect'] ) ? $a['effect'] : 'fade';
135 $type = ( isset( $a['type'] ) && 'out' === $a['type'] ) ? 'out' : 'in';
136 $dir = ! empty( $a['direction'] ) ? $a['direction'] : 'none';
137 if ( 'slide' === $effect && empty( $a['direction'] ) ) {
138 $dir = 'top';
139 }
140 return 'ablocks-a-' . $effect . '-' . $type . '-' . $dir;
141 }
142
143 protected function offset_transform( $direction, $dist ) {
144 $n = -$dist;
145 $p = $dist;
146 switch ( $direction ) {
147 case 'top': return 'translateY(' . $n . 'px)';
148 case 'bottom': return 'translateY(' . $p . 'px)';
149 case 'left': return 'translateX(' . $n . 'px)';
150 case 'right': return 'translateX(' . $p . 'px)';
151 case 'top-left': return 'translate(' . $n . 'px, ' . $n . 'px)';
152 case 'top-right': return 'translate(' . $p . 'px, ' . $n . 'px)';
153 case 'bottom-left': return 'translate(' . $n . 'px, ' . $p . 'px)';
154 case 'bottom-right': return 'translate(' . $p . 'px, ' . $p . 'px)';
155 default: return '';
156 }
157 }
158
159 protected function keyframe_css( $a ) {
160 $effect = ! empty( $a['effect'] ) ? $a['effect'] : 'fade';
161 $type = ( isset( $a['type'] ) && 'out' === $a['type'] ) ? 'out' : 'in';
162 $direction = isset( $a['direction'] ) ? $a['direction'] : '';
163 if ( 'slide' === $effect && '' === $direction ) {
164 $direction = 'top';
165 }
166 $dist = ( 'slide' === $effect ) ? 60 : 24;
167 $translate = $this->offset_transform( $direction, $dist );
168 $scale = ( 'scale' === $effect ) ? 'scale(0.85)' : '';
169 $off = trim( $translate . ' ' . $scale );
170 if ( '' === $off ) {
171 $off = 'none';
172 }
173 $name = $this->keyframe_name( $a );
174 if ( 'out' === $type ) {
175 return '@keyframes ' . $name . '{from{opacity:1;transform:none;}to{opacity:0;transform:' . $off . ';}}';
176 }
177 return '@keyframes ' . $name . '{from{opacity:0;transform:' . $off . ';}to{opacity:1;transform:none;}}';
178 }
179
180 protected function iteration_count( $a ) {
181 if ( isset( $a['repeat'] ) && 'loop' === $a['repeat'] ) {
182 return 'infinite';
183 }
184 if ( isset( $a['repeat'] ) && 'times' === $a['repeat'] ) {
185 $n = isset( $a['times'] ) ? (int) $a['times'] : 1;
186 return $n > 0 ? (string) $n : '1';
187 }
188 return '1';
189 }
190
191 protected function anim_shorthand( $a ) {
192 $dur = ( isset( $a['duration'] ) && '' !== $a['duration'] ) ? $a['duration'] : 600;
193 $del = ( isset( $a['delay'] ) && '' !== $a['delay'] ) ? $a['delay'] : 0;
194 $easing = ( isset( $a['easing'] ) && isset( self::$easing_css[ $a['easing'] ] ) ) ? self::$easing_css[ $a['easing'] ] : self::$easing_css['easeIn'];
195 return $this->keyframe_name( $a ) . ' ' . $dur . 'ms ' . $easing . ' ' . $del . 'ms ' . $this->iteration_count( $a ) . ' both';
196 }
197
198 protected function trigger_suffix( $trigger ) {
199 switch ( $trigger ) {
200 case 'hover': return ':hover';
201 case 'scrollIn': return '.ablocks-in-view';
202 case 'click': return '.ablocks-anim-active';
203 default: return '';
204 }
205 }
206
207 protected function build_animation_css( $base, $animations, $devices ) {
208 if ( empty( $animations ) || ! is_array( $animations ) ) {
209 return '';
210 }
211 $css = '';
212
213 $seen = [];
214 foreach ( $animations as $a ) {
215 $name = $this->keyframe_name( $a );
216 if ( ! isset( $seen[ $name ] ) ) {
217 $seen[ $name ] = true;
218 $css .= $this->keyframe_css( $a );
219 }
220 }
221
222 $has_scroll_in = false;
223 foreach ( $animations as $a ) {
224 if ( ( isset( $a['trigger'] ) ? $a['trigger'] : '' ) === 'scrollIn' && ( ! isset( $a['type'] ) || 'out' !== $a['type'] ) ) {
225 $has_scroll_in = true;
226 break;
227 }
228 }
229 if ( $has_scroll_in ) {
230 $css .= $base . '.ablocks-anim-scroll.ablocks-anim-ready:not(.ablocks-in-view){opacity:0;}';
231 }
232
233 foreach ( [ 'load', 'scrollIn', 'hover', 'click' ] as $trigger ) {
234 $group = array_filter( $animations, function ( $a ) use ( $trigger ) {
235 return ( isset( $a['trigger'] ) ? $a['trigger'] : 'load' ) === $trigger;
236 } );
237 if ( empty( $group ) ) {
238 continue;
239 }
240 $sel = $base . $this->trigger_suffix( $trigger );
241 $shorthand_for = function ( $device_id ) use ( $group ) {
242 $parts = [];
243 foreach ( $group as $a ) {
244 if ( empty( $a['off'][ $device_id ] ) ) {
245 $parts[] = $this->anim_shorthand( $a );
246 }
247 }
248 return implode( ', ', $parts );
249 };
250 $desktop = $shorthand_for( 'Desktop' );
251 if ( '' !== $desktop ) {
252 $css .= $sel . '{animation:' . $desktop . ';}';
253 }
254 foreach ( $devices as $d ) {
255 if ( 'Desktop' === $d['id'] || ( empty( $d['max'] ) && empty( $d['min'] ) ) ) {
256 continue;
257 }
258 $sh = $shorthand_for( $d['id'] );
259 if ( $sh !== $desktop ) {
260 $media = Helper::breakpoint_media_condition( isset( $d['min'] ) ? $d['min'] : 0, isset( $d['max'] ) ? $d['max'] : 0 );
261 if ( '' !== $media ) {
262 $css .= '@media screen and ' . $media . '{' . $sel . '{animation:' . ( '' !== $sh ? $sh : 'none' ) . ';}}';
263 }
264 }
265 }
266 }
267
268 $scroll_on = array_filter( $animations, function ( $a ) {
269 return ( isset( $a['trigger'] ) ? $a['trigger'] : '' ) === 'scrollOn';
270 } );
271 if ( ! empty( $scroll_on ) ) {
272 $scrub = function ( $device_id ) use ( $scroll_on ) {
273 $parts = [];
274 foreach ( $scroll_on as $a ) {
275 if ( empty( $a['off'][ $device_id ] ) ) {
276 $parts[] = $this->keyframe_name( $a ) . ' 1000ms linear 0ms 1 both';
277 }
278 }
279 return implode( ', ', $parts );
280 };
281 $desk = $scrub( 'Desktop' );
282 if ( '' !== $desk ) {
283 $css .= $base . '{animation:' . $desk . ';animation-play-state:paused;}';
284 }
285 foreach ( $devices as $d ) {
286 if ( 'Desktop' === $d['id'] || ( empty( $d['max'] ) && empty( $d['min'] ) ) ) {
287 continue;
288 }
289 $sh = $scrub( $d['id'] );
290 if ( $sh !== $desk ) {
291 $media = Helper::breakpoint_media_condition( isset( $d['min'] ) ? $d['min'] : 0, isset( $d['max'] ) ? $d['max'] : 0 );
292 if ( '' !== $media ) {
293 $css .= '@media screen and ' . $media . '{' . $base . '{animation:' . ( '' !== $sh ? $sh : 'none' ) . ';}}';
294 }
295 }
296 }
297 }
298
299 return $css;
300 }
301 }
302