Skip to content

Commit 7ba7db2

Browse files
committed
wip
1 parent b5576e3 commit 7ba7db2

File tree

12 files changed

+304
-2
lines changed

12 files changed

+304
-2
lines changed

src/Fields/Base.php

Lines changed: 246 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
namespace Backstage\Fields\Fields;
44

5-
use Backstage\Fields\Contracts\FieldContract;
6-
use Backstage\Fields\Models\Field;
75
use Filament\Forms;
6+
use Filament\Forms\Components\Grid;
7+
use Filament\Forms\Components\Fieldset;
8+
use Filament\Forms\Get;
9+
use Livewire\Livewire;
10+
use Backstage\Fields\Models\Field;
811
use Filament\Support\Colors\Color;
12+
use Backstage\Fields\Contracts\FieldContract;
913

1014
abstract class Base implements FieldContract
1115
{
@@ -51,6 +55,76 @@ public function getForm(): array
5155
];
5256
}
5357

58+
/**
59+
* Get the Rules form schema for conditional logic
60+
*/
61+
public function getRulesForm(): array
62+
{
63+
return [
64+
Forms\Components\Grid::make(2)
65+
->schema([
66+
Forms\Components\Fieldset::make('Conditional logic')
67+
->schema([
68+
69+
Forms\Components\Select::make('config.conditionalField')
70+
->label(__('Show/Hide based on field'))
71+
->placeholder(__('Select a field'))
72+
->searchable()
73+
->live()
74+
->options(function ($livewire) {
75+
// The $livewire parameter is actually the FieldsRelationManager
76+
if (!$livewire || !method_exists($livewire, 'getOwnerRecord')) {
77+
return [];
78+
}
79+
80+
$ownerRecord = $livewire->getOwnerRecord();
81+
82+
if (!$ownerRecord) {
83+
return [];
84+
}
85+
86+
// Get all existing fields for this owner record
87+
$fields = Field::where('model_type', 'setting')
88+
->where('model_key', $ownerRecord->getKey())
89+
->pluck('name', 'ulid')
90+
->toArray();
91+
92+
return $fields;
93+
}),
94+
Forms\Components\Select::make('config.conditionalOperator')
95+
->label(__('Condition'))
96+
->options([
97+
'equals' => __('Equals'),
98+
'not_equals' => __('Does not equal'),
99+
'contains' => __('Contains'),
100+
'not_contains' => __('Does not contain'),
101+
'starts_with' => __('Starts with'),
102+
'ends_with' => __('Ends with'),
103+
'is_empty' => __('Is empty'),
104+
'is_not_empty' => __('Is not empty'),
105+
])
106+
->visible(fn(Forms\Get $get): bool => filled($get('config.conditionalField'))),
107+
Forms\Components\TextInput::make('config.conditionalValue')
108+
->label(__('Value'))
109+
->visible(
110+
fn(Forms\Get $get): bool =>
111+
filled($get('config.conditionalField')) &&
112+
!in_array($get('config.conditionalOperator'), ['is_empty', 'is_not_empty'])
113+
),
114+
Forms\Components\Select::make('config.conditionalAction')
115+
->label(__('Action'))
116+
->options([
117+
'show' => __('Show field'),
118+
'hide' => __('Hide field'),
119+
'required' => __('Make required'),
120+
'not_required' => __('Make not required'),
121+
])
122+
->visible(fn(Forms\Get $get): bool => filled($get('config.conditionalField'))),
123+
]),
124+
]),
125+
];
126+
}
127+
54128
public static function getDefaultConfig(): array
55129
{
56130
return [
@@ -61,6 +135,10 @@ public static function getDefaultConfig(): array
61135
'hint' => null,
62136
'hintColor' => null,
63137
'hintIcon' => null,
138+
'conditionalField' => null,
139+
'conditionalOperator' => null,
140+
'conditionalValue' => null,
141+
'conditionalAction' => null,
64142
];
65143
}
66144

@@ -78,9 +156,175 @@ public static function applyDefaultSettings($input, ?Field $field = null)
78156
$input->hintColor(Color::hex($field->config['hintColor']));
79157
}
80158

159+
// Apply conditional logic
160+
$input = self::applyConditionalLogic($input, $field);
161+
162+
// Apply conditional validation rules
163+
$input = self::applyConditionalValidation($input, $field);
164+
165+
return $input;
166+
}
167+
168+
/**
169+
* Apply conditional visibility and required logic to the input
170+
*/
171+
protected static function applyConditionalLogic($input, ?Field $field = null): mixed
172+
{
173+
if (!$field || empty($field->config['conditionalField']) || empty($field->config['conditionalAction'])) {
174+
return $input;
175+
}
176+
177+
$conditionalField = $field->config['conditionalField'];
178+
$operator = $field->config['conditionalOperator'] ?? 'equals';
179+
$value = $field->config['conditionalValue'] ?? null;
180+
$action = $field->config['conditionalAction'];
181+
182+
// Get the field name for the conditional field
183+
$conditionalFieldName = self::getFieldNameFromUlid($conditionalField, $field);
184+
185+
if (!$conditionalFieldName) {
186+
return $input;
187+
}
188+
189+
switch ($action) {
190+
case 'show':
191+
$input->visible(
192+
fn(Forms\Get $get): bool =>
193+
self::evaluateCondition($get($conditionalFieldName), $operator, $value)
194+
);
195+
break;
196+
197+
case 'hide':
198+
$input->visible(
199+
fn(Forms\Get $get): bool =>
200+
!self::evaluateCondition($get($conditionalFieldName), $operator, $value)
201+
);
202+
break;
203+
204+
case 'required':
205+
$input->required(
206+
fn(Forms\Get $get): bool =>
207+
self::evaluateCondition($get($conditionalFieldName), $operator, $value)
208+
);
209+
break;
210+
211+
case 'not_required':
212+
$input->required(
213+
fn(Forms\Get $get): bool =>
214+
!self::evaluateCondition($get($conditionalFieldName), $operator, $value)
215+
);
216+
break;
217+
}
218+
81219
return $input;
82220
}
83221

222+
/**
223+
* Apply conditional validation rules
224+
*/
225+
protected static function applyConditionalValidation($input, ?Field $field = null): mixed
226+
{
227+
if (!$field || empty($field->config['conditionalField']) || empty($field->config['conditionalAction'])) {
228+
return $input;
229+
}
230+
231+
$conditionalField = $field->config['conditionalField'];
232+
$operator = $field->config['conditionalOperator'] ?? 'equals';
233+
$value = $field->config['conditionalValue'] ?? null;
234+
$action = $field->config['conditionalAction'];
235+
236+
// Get the field name for the conditional field
237+
$conditionalFieldName = self::getFieldNameFromUlid($conditionalField, $field);
238+
239+
if (!$conditionalFieldName) {
240+
return $input;
241+
}
242+
243+
// Apply Filament validation rules based on conditional logic
244+
switch ($action) {
245+
case 'required':
246+
if ($operator === 'equals' && $value !== null) {
247+
$input->requiredIf($conditionalFieldName, $value);
248+
} elseif ($operator === 'not_equals' && $value !== null) {
249+
$input->requiredUnless($conditionalFieldName, $value);
250+
} elseif ($operator === 'is_empty') {
251+
$input->requiredUnless($conditionalFieldName, '');
252+
} elseif ($operator === 'is_not_empty') {
253+
$input->requiredIf($conditionalFieldName, '');
254+
}
255+
break;
256+
257+
case 'not_required':
258+
// For not_required, we don't apply validation rules as the field is optional
259+
break;
260+
}
261+
262+
return $input;
263+
}
264+
265+
/**
266+
* Evaluate the conditional logic
267+
*/
268+
protected static function evaluateCondition($fieldValue, string $operator, $expectedValue): bool
269+
{
270+
switch ($operator) {
271+
case 'equals':
272+
return $fieldValue == $expectedValue;
273+
274+
case 'not_equals':
275+
return $fieldValue != $expectedValue;
276+
277+
case 'contains':
278+
return is_string($fieldValue) && str_contains($fieldValue, $expectedValue);
279+
280+
case 'not_contains':
281+
return is_string($fieldValue) && !str_contains($fieldValue, $expectedValue);
282+
283+
case 'starts_with':
284+
return is_string($fieldValue) && str_starts_with($fieldValue, $expectedValue);
285+
286+
case 'ends_with':
287+
return is_string($fieldValue) && str_ends_with($fieldValue, $expectedValue);
288+
289+
case 'is_empty':
290+
return empty($fieldValue);
291+
292+
case 'is_not_empty':
293+
return !empty($fieldValue);
294+
295+
default:
296+
return false;
297+
}
298+
}
299+
300+
/**
301+
* Get the field name from ULID for form access
302+
*/
303+
protected static function getFieldNameFromUlid(string $ulid, Field $currentField): ?string
304+
{
305+
// Find the conditional field
306+
$conditionalField = Field::find($ulid);
307+
308+
if (!$conditionalField) {
309+
return null;
310+
}
311+
312+
// Get the record that owns these fields
313+
// Load the model relationship if it's not already loaded
314+
if (!$currentField->relationLoaded('model')) {
315+
$currentField->load('model');
316+
}
317+
318+
$record = $currentField->model;
319+
320+
if (!$record || !isset($record->valueColumn)) {
321+
return null;
322+
}
323+
324+
// Return the field name in the format used by the form
325+
return "{$record->valueColumn}.{$ulid}";
326+
}
327+
84328
protected static function ensureArray($value, string $delimiter = ','): array
85329
{
86330
if (is_array($value)) {

src/Fields/Checkbox.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ public function getForm(): array
6565
->inline(false),
6666
]),
6767
]),
68+
Forms\Components\Tabs\Tab::make('Rules')
69+
->label(__('Rules'))
70+
->schema([
71+
...parent::getRulesForm(),
72+
]),
6873
])->columnSpanFull(),
6974
];
7075
}

src/Fields/CheckboxList.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ public function getForm(): array
106106
->visible(fn (Forms\Get $get): bool => $get('config.searchable')),
107107
]),
108108
]),
109+
Forms\Components\Tabs\Tab::make('Rules')
110+
->label(__('Rules'))
111+
->schema([
112+
...parent::getRulesForm(),
113+
]),
109114
])->columnSpanFull(),
110115
];
111116
}

src/Fields/Color.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ public function getForm(): array
6060
->placeholder(__('Enter a regex pattern')),
6161
]),
6262
]),
63+
Forms\Components\Tabs\Tab::make('Rules')
64+
->label(__('Rules'))
65+
->schema([
66+
...parent::getRulesForm(),
67+
]),
6368
])->columnSpanFull(),
6469
];
6570
}

src/Fields/DateTime.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ public function getForm(): array
118118
]),
119119
self::affixFormFields(),
120120
]),
121+
Forms\Components\Tabs\Tab::make('Rules')
122+
->label(__('Rules'))
123+
->schema([
124+
...parent::getRulesForm(),
125+
]),
121126
])->columnSpanFull(),
122127
];
123128
}

src/Fields/MarkdownEditor.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ public function getForm(): array
7272
]),
7373
]),
7474
]),
75+
Forms\Components\Tabs\Tab::make('Rules')
76+
->label(__('Rules'))
77+
->schema([
78+
...parent::getRulesForm(),
79+
]),
7580
])->columnSpanFull(),
7681
];
7782
}

src/Fields/Radio.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public function getForm(): array
6161
->inline(false),
6262
self::optionFormFields(),
6363
])->columns(3),
64+
Forms\Components\Tabs\Tab::make('Rules')
65+
->label(__('Rules'))
66+
->schema([
67+
...parent::getRulesForm(),
68+
]),
6469
])->columnSpanFull(),
6570
];
6671
}

src/Fields/RichEditor.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ public function getForm(): array
130130
->columnSpanFull(),
131131
]),
132132
]),
133+
Forms\Components\Tabs\Tab::make('Rules')
134+
->label(__('Rules'))
135+
->schema([
136+
...parent::getRulesForm(),
137+
]),
133138
])->columnSpanFull(),
134139
];
135140
}

src/Fields/Select.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,11 @@ public function getForm(): array
205205
->visible(fn (Forms\Get $get): bool => $get('config.searchable')),
206206
]),
207207
]),
208+
Forms\Components\Tabs\Tab::make('Rules')
209+
->label(__('Rules'))
210+
->schema([
211+
...parent::getRulesForm(),
212+
]),
208213
])->columnSpanFull(),
209214
];
210215
}

src/Fields/Text.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
use Backstage\Fields\Models\Field;
99
use Filament\Forms;
1010
use Filament\Forms\Components\TextInput as Input;
11+
use Filament\Forms\Get;
12+
use Filament\Forms\Components\Tabs;
13+
use Filament\Forms\Components\Grid;
1114

1215
class Text extends Base implements FieldContract
1316
{
@@ -163,6 +166,11 @@ public function getForm(): array
163166
->visible(fn (Forms\Get $get): bool => $get('config.type') === 'tel'),
164167
]),
165168
]),
169+
Forms\Components\Tabs\Tab::make('Rules')
170+
->label(__('Rules'))
171+
->schema([
172+
...parent::getRulesForm(),
173+
]),
166174
])->columnSpanFull(),
167175
];
168176
}

0 commit comments

Comments
 (0)