22
33namespace Backstage \Fields \Fields ;
44
5- use Backstage \Fields \Contracts \FieldContract ;
6- use Backstage \Fields \Models \Field ;
75use 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 ;
811use Filament \Support \Colors \Color ;
12+ use Backstage \Fields \Contracts \FieldContract ;
913
1014abstract 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 )) {
0 commit comments