PluginProbe
The Innovative Form Builder – IvyForms / 1.1
The Innovative Form Builder – IvyForms v1.1
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / backend / src / Services / Form / Style / FieldStateStylesApplier.php

FieldStateStylesApplier.php in The Innovative Form Builder – IvyForms 1.1, at backend/src/Services/Form/Style/FieldStateStylesApplier.php

416 lines 14.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @copyright © Melograno Venture Studio. All rights reserved.
5 * @licence See LICENCE.md for license details.
6 */
7
8 namespace IvyForms\Services\Form\Style;
9
10 // phpcs:disable PSR1.Files.SideEffects
11
12 if (!defined('ABSPATH')) {
13 exit; // Exit if accessed directly
14 }
15
16 /**
17 * Class FieldStateStylesApplier
18 *
19 * Applies state-based field styles (active, read-only, error).
20 *
21 * @package IvyForms\Services\Form\StyleHelpers
22 */
23 class FieldStateStylesApplier
24 {
25 /**
26 * Apply field state styles
27 *
28 * @param CssBuilder $css
29 * @param string $baseSelector
30 * @param array<string, mixed> $styleSettings
31 * @return void
32 */
33 public function apply(CssBuilder $css, string $baseSelector, array $styleSettings): void
34 {
35 $this->addActiveState($css, $baseSelector, $styleSettings);
36 $this->addReadOnlyState($css, $baseSelector, $styleSettings);
37 $this->addErrorState($css, $baseSelector, $styleSettings);
38 }
39
40 /**
41 * Add active (focused) state
42 *
43 * @param CssBuilder $css
44 * @param string $baseSelector
45 * @param array<string, mixed> $styleSettings
46 * @return void
47 */
48 private function addActiveState(
49 CssBuilder $css,
50 string $baseSelector,
51 array $styleSettings
52 ): void {
53 $stateValues = $this->getFieldStateValues($styleSettings, 'active');
54 $background = $stateValues['background'];
55 $borderColor = $stateValues['borderColor'];
56 $borderStyle = $stateValues['borderStyle'];
57 $textColor = $stateValues['textColor'];
58 $descriptionColor = $stateValues['descriptionColor'];
59
60 if (
61 !$this->hasAnyCoreStateValue($background, $borderColor, $textColor)
62 && !$this->isSetValue($descriptionColor)
63 ) {
64 return;
65 }
66
67 $this->addBackgroundAndBorderRule(
68 $css,
69 $this->getActiveStateSelectors($baseSelector),
70 $background,
71 $borderColor,
72 $borderStyle
73 );
74
75 $this->addSimpleColorRule(
76 $css,
77 [
78 $baseSelector . ' .ivyforms-input.el-input:focus-within input',
79 $baseSelector . ' .ivyforms-field:focus-within input',
80 $baseSelector . ' .ivyforms-field:focus-within textarea',
81 $baseSelector . ' .ivyforms-field:focus-within .vti__input',
82 $baseSelector . ' .ivyforms-field:focus-within .el-input__inner',
83 $baseSelector . ' .ivyforms-field:focus-within .el-textarea__inner',
84 ],
85 $textColor
86 );
87
88 $this->addDescriptionColorRule(
89 $css,
90 [
91 $baseSelector . ' .ivyforms-form-item:focus-within .ivyforms-form-item__info-text',
92 $baseSelector . ' .el-form-item:focus-within .ivyforms-form-item__info-text',
93 ],
94 $descriptionColor
95 );
96 }
97
98 /**
99 * Add readonly state
100 *
101 * @param CssBuilder $css
102 * @param string $baseSelector
103 * @param array<string, mixed> $styleSettings
104 * @return void
105 */
106 private function addReadOnlyState(
107 CssBuilder $css,
108 string $baseSelector,
109 array $styleSettings
110 ): void {
111 $stateValues = $this->getFieldStateValues($styleSettings, 'readOnly');
112 $background = $stateValues['background'];
113 $borderColor = $stateValues['borderColor'];
114 $borderStyle = $stateValues['borderStyle'];
115 $textColor = $stateValues['textColor'];
116 $descriptionColor = $stateValues['descriptionColor'];
117
118 $hasCoreStateValue = $this->hasAnyCoreStateValue($background, $borderColor, $textColor);
119
120 if (!$hasCoreStateValue && !$this->isSetValue($descriptionColor)) {
121 return;
122 }
123
124 if ($hasCoreStateValue && !$this->isSetValue($textColor)) {
125 $placeholderColor = $styleSettings['advancedMode']['fieldPlaceholder']['fontColor'] ?? null;
126 if ($this->isSetValue($placeholderColor)) {
127 $textColor = $placeholderColor;
128 }
129 }
130
131 $isReadOnlyBackgroundBorderApplied = $this->addBackgroundAndBorderRule(
132 $css,
133 $this->getReadOnlyStateSelectors($baseSelector),
134 $background,
135 $borderColor,
136 $borderStyle
137 );
138
139 if ($isReadOnlyBackgroundBorderApplied) {
140 $this->addBackgroundAndBorderRule(
141 $css,
142 [
143 $baseSelector . ' .ivyforms-field.is-readonly .vue-tel-input',
144 $baseSelector . ' .ivyforms-field.is-readonly .vti__input',
145 ],
146 $background,
147 $borderColor,
148 $borderStyle
149 );
150 }
151
152 $this->addSimpleColorRule(
153 $css,
154 [
155 $baseSelector . ' .ivyforms-field.is-readonly input',
156 $baseSelector . ' .ivyforms-field.is-readonly textarea',
157 $baseSelector . ' .ivyforms-field.is-readonly .el-input__inner',
158 $baseSelector . ' .ivyforms-field.is-readonly .el-textarea__inner',
159 $baseSelector . ' .ivyforms-field.is-readonly .vti__input',
160 $baseSelector . ' .el-input.is-disabled .el-input__inner',
161 $baseSelector . ' .el-textarea.is-disabled .el-textarea__inner',
162 ],
163 $textColor
164 );
165
166 $this->addDescriptionColorRule(
167 $css,
168 [
169 $baseSelector . ' .ivyforms-field.is-readonly .ivyforms-form-item__info-text',
170 $baseSelector . ' .ivyforms-field.is-disabled .ivyforms-form-item__info-text',
171 $baseSelector . ' .ivyforms-field.is-readonly .el-form-item .ivyforms-form-item__info-text',
172 $baseSelector . ' .ivyforms-field.is-disabled .el-form-item .ivyforms-form-item__info-text',
173 ],
174 $descriptionColor
175 );
176 }
177
178 /**
179 * Add error state
180 *
181 * @param CssBuilder $css
182 * @param string $baseSelector
183 * @param array<string, mixed> $styleSettings
184 * @return void
185 */
186 private function addErrorState(
187 CssBuilder $css,
188 string $baseSelector,
189 array $styleSettings
190 ): void {
191 $stateValues = $this->getFieldStateValues($styleSettings, 'error');
192 $background = $stateValues['background'];
193 $borderColor = $stateValues['borderColor'];
194 $borderStyle = $stateValues['borderStyle'];
195 $textColor = $stateValues['textColor'];
196 $descriptionColor = $stateValues['descriptionColor'];
197
198 if (
199 !$this->hasAnyCoreStateValue($background, $borderColor, $textColor)
200 && !$this->isSetValue($descriptionColor)
201 ) {
202 return;
203 }
204
205 $this->addBackgroundAndBorderRule(
206 $css,
207 [
208 $baseSelector . ' .ivyforms-form-item.is-error .ivyforms-input.el-input .el-input__wrapper',
209 $baseSelector . ' .ivyforms-form-item.is-error .ivyforms-field .el-input__wrapper',
210 $baseSelector . ' .ivyforms-form-item.is-error .el-input__wrapper',
211 $baseSelector . ' .ivyforms-form-item.is-error .el-textarea__inner',
212 $baseSelector . ' .ivyforms-form-item.is-error .el-select__wrapper',
213 $baseSelector . ' .ivyforms-form-item.is-error .vue-tel-input',
214 ],
215 $background,
216 $borderColor,
217 $borderStyle
218 );
219
220 if ($this->isSetValue($textColor)) {
221 $this->addSimpleColorRule(
222 $css,
223 [
224 $baseSelector . ' .ivyforms-form-item.is-error .ivyforms-input.el-input input',
225 $baseSelector . ' .ivyforms-form-item.is-error .ivyforms-field input',
226 $baseSelector . ' .ivyforms-form-item.is-error input',
227 $baseSelector . ' .ivyforms-form-item.is-error textarea',
228 $baseSelector . ' .ivyforms-form-item.is-error .el-input__inner',
229 $baseSelector . ' .ivyforms-form-item.is-error .el-textarea__inner',
230 $baseSelector . ' .ivyforms-form-item.is-error .vti__input',
231 $baseSelector . ' .ivyforms-form-item.is-error .el-input__prefix',
232 $baseSelector . ' .ivyforms-form-item.is-error .el-input__suffix',
233 $baseSelector . ' .ivyforms-form-item.is-error .el-input__prefix .el-icon',
234 $baseSelector . ' .ivyforms-form-item.is-error .el-input__suffix .el-icon',
235 ],
236 $textColor
237 );
238
239 // Override icon color via CSS variable
240 $errorIconSelectors = [$baseSelector . ' .ivyforms-form-item.is-error .ivyforms-icon'];
241 $iconStyles = [' --map-status-error-symbol-0: ' . esc_attr($textColor) . ' !important;'];
242 $css->addRule($errorIconSelectors, $iconStyles);
243
244 // Override SVG fill colors directly
245 $errorSvgSelectors = [$baseSelector . ' .ivyforms-form-item.is-error .ivyforms-icon svg path'];
246 $svgStyles = [' fill: ' . esc_attr($textColor) . ' !important;'];
247 $css->addRule($errorSvgSelectors, $svgStyles);
248 }
249
250 $this->addDescriptionColorRule(
251 $css,
252 [
253 $baseSelector . ' .ivyforms-form-item.is-error .ivyforms-form-item__info-text',
254 $baseSelector . ' .el-form-item.is-error .ivyforms-form-item__info-text',
255 ],
256 $descriptionColor
257 );
258 }
259
260 /**
261 * Check if any core state value exists (background, border, text).
262 *
263 * @param mixed $background
264 * @param mixed $borderColor
265 * @param mixed $textColor
266 * @return bool
267 */
268 private function hasAnyCoreStateValue($background, $borderColor, $textColor): bool
269 {
270 return $this->isSetValue($background)
271 || $this->isSetValue($borderColor)
272 || $this->isSetValue($textColor);
273 }
274
275 /**
276 * Get normalized field state values.
277 *
278 * @param array<string, mixed> $styleSettings
279 * @param string $stateName
280 * @return array<string, mixed>
281 */
282 private function getFieldStateValues(array $styleSettings, string $stateName): array
283 {
284 $stateSettings = $styleSettings['advancedMode']['fieldStates'][$stateName] ?? [];
285
286 return [
287 'background' => $stateSettings['background'] ?? null,
288 'borderColor' => $stateSettings['borderColor'] ?? null,
289 'borderStyle' => $stateSettings['borderStyle'] ?? 'solid',
290 'textColor' => $stateSettings['textColor'] ?? null,
291 'descriptionColor' => $stateSettings['descriptionColor'] ?? null,
292 ];
293 }
294
295 /**
296 * Check whether a value is considered set.
297 *
298 * @param mixed $value
299 * @return bool
300 */
301 private function isSetValue($value): bool
302 {
303 return !empty($value);
304 }
305
306 /**
307 * Add a background and border state rule.
308 *
309 * @param CssBuilder $css
310 * @param string[] $selectors
311 * @param mixed $background
312 * @param mixed $borderColor
313 * @param mixed $borderStyle
314 * @return bool
315 */
316 private function addBackgroundAndBorderRule(
317 CssBuilder $css,
318 array $selectors,
319 $background,
320 $borderColor,
321 $borderStyle
322 ): bool {
323 if (!$this->isSetValue($background) && !$this->isSetValue($borderColor)) {
324 return false;
325 }
326
327 $styles = [];
328
329 if ($this->isSetValue($background)) {
330 $styles[] = ' background-color: ' . esc_attr($background) . ' !important;';
331 }
332
333 if ($this->isSetValue($borderColor)) {
334 $styles[] = ' border-color: ' . esc_attr($borderColor) . ' !important;';
335 $styles[] = ' border-style: ' . esc_attr($borderStyle) . ' !important;';
336 }
337
338 $css->addRule($selectors, $styles);
339
340 return true;
341 }
342
343 /**
344 * Add a simple text color rule if color is set.
345 *
346 * @param CssBuilder $css
347 * @param string[] $selectors
348 * @param mixed $color
349 * @return void
350 */
351 private function addSimpleColorRule(CssBuilder $css, array $selectors, $color): void
352 {
353 if (!$this->isSetValue($color)) {
354 return;
355 }
356
357 $css->addRule($selectors, [' color: ' . esc_attr($color) . ' !important;']);
358 }
359
360 /**
361 * Add description color and corresponding CSS variable if color is set.
362 *
363 * @param CssBuilder $css
364 * @param string[] $selectors
365 * @param mixed $descriptionColor
366 * @return void
367 */
368 private function addDescriptionColorRule(
369 CssBuilder $css,
370 array $selectors,
371 $descriptionColor
372 ): void {
373 if (!$this->isSetValue($descriptionColor)) {
374 return;
375 }
376
377 $css->addRule($selectors, [
378 ' color: ' . esc_attr($descriptionColor) . ' !important;',
379 ' --ivyforms-info-color: ' . esc_attr($descriptionColor) . ' !important;',
380 ]);
381 }
382
383 /**
384 * Get active state selectors
385 *
386 * @param string $baseSelector
387 * @return string[]
388 */
389 private function getActiveStateSelectors(string $baseSelector): array
390 {
391 return [
392 $baseSelector . ' .ivyforms-input.el-input:focus-within .el-input__wrapper',
393 $baseSelector . ' .ivyforms-field__text:focus-within .el-input__wrapper',
394 $baseSelector . ' .ivyforms-field__email:focus-within .el-input__wrapper',
395 $baseSelector . ' .ivyforms-field__textarea:focus-within .el-textarea__inner',
396 $baseSelector . ' .ivyforms-field__select:focus-within .el-select__wrapper',
397 ];
398 }
399
400 /**
401 * Get readonly state selectors
402 *
403 * @param string $baseSelector
404 * @return string[]
405 */
406 private function getReadOnlyStateSelectors(string $baseSelector): array
407 {
408 return [
409 $baseSelector . ' .ivyforms-input.el-input.is-disabled .el-input__wrapper',
410 $baseSelector . ' .ivyforms-field.is-disabled .el-input__wrapper',
411 $baseSelector . ' .ivyforms-field.is-readonly .el-input__wrapper',
412 $baseSelector . ' .ivyforms-field.is-disabled .el-textarea__inner',
413 ];
414 }
415 }
416