From eeedbc5ef424fa7815417a8d91bbd1036a8a1586 Mon Sep 17 00:00:00 2001 From: MiddleSokilAI Date: Wed, 8 Jul 2026 01:15:59 +0300 Subject: [PATCH] fix(modal): support conditional fields and addons --- src/Livewire/ModuleTable.php | 15 +++++- tests/run.php | 16 +++++++ .../table/module/modal-field.blade.php | 48 +++++++++++++++++-- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/Livewire/ModuleTable.php b/src/Livewire/ModuleTable.php index 6d93686..1ddb6b7 100644 --- a/src/Livewire/ModuleTable.php +++ b/src/Livewire/ModuleTable.php @@ -262,7 +262,7 @@ public function saveModal(): void return; } - $this->validate($this->modalRules(), [], $this->modalValidationAttributes()); + $this->validateModalData(); if (($method = $this->modalConfig('save_provider')) && method_exists($this->provider(), $method)) { $savedId = $this->callProvider((string) $method, $this->modalData, $this->modalRecordId, $this->modalMode); @@ -292,7 +292,7 @@ public function saveModalAction(string $key): void data_set($this->modalData, (string) $field, $value); } - $this->validate($this->modalRules(), [], $this->modalValidationAttributes()); + $this->validateModalData(); $method = (string) ($action['save_provider'] ?? $this->modalConfig('save_provider', 'saveModal')); if (!method_exists($this->provider(), $method)) { @@ -1574,6 +1574,17 @@ protected function modalRules(): array return $rules; } + protected function validateModalData(): void + { + $rules = $this->modalRules(); + + if ($rules === []) { + return; + } + + $this->validate($rules, [], $this->modalValidationAttributes()); + } + /** @return array */ protected function modalValidationAttributes(): array { diff --git a/tests/run.php b/tests/run.php index 11090b7..ff125aa 100644 --- a/tests/run.php +++ b/tests/run.php @@ -1543,6 +1543,16 @@ function evo_ui_config(string $path): array evo_ui_assert_contains('$withActionKey', $list, 'List row actions must support action-key arguments like table rows.'); }); + evo_ui_test('module modal skips Livewire validation when no field rules are configured', function (): void { + $moduleTable = evo_ui_read('src/Livewire/ModuleTable.php'); + + evo_ui_assert_contains('protected function validateModalData(): void', $moduleTable, 'ModuleTable must centralize modal validation.'); + evo_ui_assert_contains('$rules = $this->modalRules();', $moduleTable, 'ModuleTable modal validation must read configured field rules.'); + evo_ui_assert_contains('if ($rules === []) {' . PHP_EOL . ' return;' . PHP_EOL . ' }', $moduleTable, 'ModuleTable must skip Livewire validation when modal rules are empty.'); + evo_ui_assert_contains('$this->validate($rules, [], $this->modalValidationAttributes());', $moduleTable, 'ModuleTable must still validate when modal rules exist.'); + evo_ui_assert_not_contains('$this->validate($this->modalRules(), [], $this->modalValidationAttributes());', $moduleTable, 'ModuleTable must not call Livewire validation with an empty rules array.'); + }); + evo_ui_test('module table exposes filtering, sorting, pagination and view state methods', function (): void { $moduleTable = evo_ui_read('src/Livewire/ModuleTable.php'); $moduleTableView = evo_ui_read('views/components/table/module.blade.php'); @@ -2119,6 +2129,12 @@ function evo_ui_config(string $path): array '$modalNotices' => 'Modal form must support configured notice blocks.', 'evo-ui-modal__notices' => 'Modal notices must render in a dedicated container.', 'placeholder="{{ $placeholder }}"' => 'Top-level modal fields must render configured placeholders.', + 'visible_if_all' => 'Modal fields must support multiple visibility conditions.', + "->implode(' && ')" => 'Multiple visibility conditions must be combined with AND logic.', + 'visible_if_any' => 'Modal fields must support alternative visibility conditions.', + "->implode(' || ')" => 'Alternative visibility conditions must be combined with OR logic.', + 'addon_prefix' => 'Modal text fields must support input add-on prefixes.', + 'evo-ui-input-group__addon' => 'Modal input add-ons must render a visible prefix.', 'EvoUI.browseImageField' => 'Image fields must open through the shared media bridge.', 'EvoUI.browseMediaField' => 'File fields must open through the shared media bridge.', 'EvoUI.syncRichEditors($el, $wire).then(() => $wire.saveModal())' => 'Modal submit must sync editors before save.', diff --git a/views/components/table/module/modal-field.blade.php b/views/components/table/module/modal-field.blade.php index d1a7344..4f401d5 100644 --- a/views/components/table/module/modal-field.blade.php +++ b/views/components/table/module/modal-field.blade.php @@ -7,6 +7,7 @@ $help = $field['help'] ?? $field['description'] ?? null; $helpText = $help ? __((string) $help) : ''; $placeholder = !empty($field['placeholder']) ? __((string) $field['placeholder']) : ''; + $addonPrefix = (string) ($field['addon_prefix'] ?? ''); $fieldIdSuffix = (string) ($field['id_suffix'] ?? ''); $fieldId = 'evo-modal-' . preg_replace('/[^a-z0-9_-]/i', '-', trim($controller->preset . '-' . $name . '-' . $fieldIdSuffix, '-')); $section = preg_replace('/[^a-z0-9_-]/i', '-', (string) ($field['section'] ?? '')); @@ -22,6 +23,12 @@ $gridRow = trim((string) ($field['grid_row'] ?? '')); $fieldStyle = preg_match('/^\d+(?:\s*\/\s*\d+)?$/', $gridRow) ? 'grid-row: ' . $gridRow . ';' : ''; $visibleIf = is_array($field['visible_if'] ?? null) ? $field['visible_if'] : []; + $visibleIfAll = collect((array) ($field['visible_if_all'] ?? [])) + ->filter(fn ($condition) => is_array($condition) && !empty($condition['field'])) + ->values(); + $visibleIfAny = collect((array) ($field['visible_if_any'] ?? [])) + ->filter(fn ($condition) => is_array($condition) && !empty($condition['field'])) + ->values(); $visibleIfField = (string) ($visibleIf['field'] ?? ''); $visibleIfExpected = $visibleIf['value'] ?? true; $visibilityExpression = ''; @@ -41,6 +48,32 @@ : $fieldExpression; } + if ($visibleIfAll->isNotEmpty()) { + $fieldExpression = $visibleIfAll + ->map(fn ($condition) => 'fieldVisible(' + . json_encode((string) ($condition['field'] ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) + . ', ' + . json_encode($condition['value'] ?? true, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) + . ')') + ->implode(' && '); + $visibilityExpression = $visibilityExpression !== '' + ? '(' . $visibilityExpression . ') && (' . $fieldExpression . ')' + : '(' . $fieldExpression . ')'; + } + + if ($visibleIfAny->isNotEmpty()) { + $fieldExpression = $visibleIfAny + ->map(fn ($condition) => 'fieldVisible(' + . json_encode((string) ($condition['field'] ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) + . ', ' + . json_encode($condition['value'] ?? true, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) + . ')') + ->implode(' || '); + $visibilityExpression = $visibilityExpression !== '' + ? '(' . $visibilityExpression . ') && (' . $fieldExpression . ')' + : '(' . $fieldExpression . ')'; + } + $colorFallback = (string) ($field['default'] ?? '#64748B'); $colorFallback = preg_match('/^#[0-9a-f]{6}$/i', $colorFallback) ? strtoupper($colorFallback) : '#64748B'; @endphp @@ -912,10 +945,19 @@ class="evo-ui-textarea evo-ui-textarea--editor" @php $inputType = in_array($type, ['email', 'number', 'date', 'datetime-local'], true) ? $type : 'text'; @endphp - @if(!empty($field['live'])) - + @if($addonPrefix !== '') + + {{ $addonPrefix }} + @if(!empty($field['live'])) + + @else + + @endif + + @elseif(!empty($field['live'])) + @else - + @endif @endif