Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ All notable changes to `custom-fields` will be documented in this file.
- **RichTextColumn**: Better rich editor display in tables
- **Tags Filter**: Colored badges and filters for tags input
- **Upgrade Command**: Automated migration tool (`vendor/bin/custom-fields-upgrade`)
- **Optional Sections**: `SYSTEM_SECTIONS_ENABLED` feature flag for sectionless mode
- **Optional Sections**: `SYSTEM_SECTIONS` feature flag for sectionless mode
- **Field Search**: Search functionality in custom fields management page
- **Field Deactivation**: Soft-disable non-system-defined fields and sections
- **Avatar Configuration**: Display options for entity references
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
],
"require": {
"php": "^8.3",
"filament/filament": "^5.0",
"filament/filament": "^4.0|^5.0",
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Filament version constraint change from "^5.0" to "^4.0|^5.0" introduces backward compatibility with Filament v4. This is a significant change that should be thoroughly tested to ensure all features work correctly in both v4 and v5. Consider documenting any known differences or limitations when running on v4 vs v5.

Suggested change
"filament/filament": "^4.0|^5.0",
"filament/filament": "^5.0",

Copilot uses AI. Check for mistakes.
"kirschbaum-development/eloquent-power-joins": "^4.0",
"postare/blade-mdi": "^1.0",
"propaganistas/laravel-phone": "^6.0",
Expand Down
2 changes: 1 addition & 1 deletion config/custom-fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
CustomFieldsFeature::UI_TABLE_FILTERS,
CustomFieldsFeature::UI_FIELD_WIDTH_CONTROL,
CustomFieldsFeature::SYSTEM_MANAGEMENT_INTERFACE,
CustomFieldsFeature::SYSTEM_SECTIONS_ENABLED,
CustomFieldsFeature::SYSTEM_SECTIONS,
)
->disable(
CustomFieldsFeature::SYSTEM_MULTI_TENANCY,
Expand Down
6 changes: 3 additions & 3 deletions database/migrations/create_custom_fields_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
{
public function up(): void
{
$sectionsEnabled = FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS_ENABLED);
$sectionsEnabled = FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS);

/**
* Custom Field Sections (only created when SYSTEM_SECTIONS_ENABLED)
* Custom Field Sections (only created when SYSTEM_SECTIONS)
*/
if ($sectionsEnabled) {
Schema::create(config('custom-fields.database.table_names.custom_field_sections'), function (Blueprint $table): void {
Expand Down Expand Up @@ -170,7 +170,7 @@ public function down(): void
Schema::dropIfExists(config('custom-fields.database.table_names.custom_field_options'));
Schema::dropIfExists(config('custom-fields.database.table_names.custom_fields'));

if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS_ENABLED)) {
if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS)) {
Schema::dropIfExists(config('custom-fields.database.table_names.custom_field_sections'));
}
}
Expand Down
2 changes: 1 addition & 1 deletion docs/content/2.essentials/1.configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ The package supports these features that can be enabled/disabled:
| `UI_FIELD_WIDTH_CONTROL` | Custom field width per field |
| `SYSTEM_MANAGEMENT_INTERFACE` | Enable the management interface |
| `SYSTEM_MULTI_TENANCY` | Enable multi-tenant isolation |
| `SYSTEM_SECTIONS_ENABLED` | Enable field grouping in sections |
| `SYSTEM_SECTIONS` | Enable field grouping in sections |

::alert{type="info"}
If your custom models include tenant-specific scoping logic, you'll need to register a [custom tenant resolver](#custom-tenant-resolution) to ensure validation works correctly.
Expand Down
4 changes: 2 additions & 2 deletions resources/boost/skills/custom-fields-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ use Relaticle\CustomFields\FeatureSystem\FeatureConfigurator;
CustomFieldsFeature::FIELD_OPTION_COLORS,
CustomFieldsFeature::UI_TABLE_COLUMNS,
CustomFieldsFeature::UI_TABLE_FILTERS,
CustomFieldsFeature::SYSTEM_SECTIONS_ENABLED,
CustomFieldsFeature::SYSTEM_SECTIONS,
)
->disable(
CustomFieldsFeature::SYSTEM_MULTI_TENANCY,
Expand All @@ -214,7 +214,7 @@ use Relaticle\CustomFields\FeatureSystem\FeatureConfigurator;
| `UI_TOGGLEABLE_COLUMNS` | Allow users to toggle column visibility |
| `UI_FIELD_WIDTH_CONTROL` | Control field width in forms |
| `SYSTEM_MANAGEMENT_INTERFACE` | Admin page for managing fields |
| `SYSTEM_SECTIONS_ENABLED` | Organize fields into sections |
| `SYSTEM_SECTIONS` | Organize fields into sections |
| `SYSTEM_MULTI_TENANCY` | Tenant isolation for fields |

## Configuration
Expand Down
9 changes: 8 additions & 1 deletion src/Data/CustomFieldSectionSettingsData.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
<?php

declare(strict_types=1);

namespace Relaticle\CustomFields\Data;

use Spatie\LaravelData\Attributes\MapName;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;

#[MapName(SnakeCaseMapper::class)]
class CustomFieldSectionSettingsData extends Data {}
class CustomFieldSectionSettingsData extends Data
{
public function __construct(
public VisibilityData $visibility = new VisibilityData,
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using new VisibilityData as a default parameter value may cause issues with Spatie Laravel Data. When deserializing data, this creates a new instance that might not match the expected serialization format. Consider using ?VisibilityData $visibility = null instead, or remove the default value entirely and handle null cases where this property is accessed.

Suggested change
public VisibilityData $visibility = new VisibilityData,
public ?VisibilityData $visibility = null,

Copilot uses AI. Check for mistakes.
) {}
}
12 changes: 12 additions & 0 deletions src/Data/VisibilityConditionData.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Relaticle\CustomFields\Data;

use Relaticle\CustomFields\Enums\ConditionSource;
use Relaticle\CustomFields\Enums\VisibilityOperator;
use Spatie\LaravelData\Attributes\MapName;
use Spatie\LaravelData\Data;
Expand All @@ -16,5 +17,16 @@ public function __construct(
public string $field_code,
public VisibilityOperator $operator,
public mixed $value,
public ConditionSource $source = ConditionSource::CustomField,
) {}

public function isModelAttribute(): bool
{
return $this->source === ConditionSource::ModelAttribute;
}

public function isCustomField(): bool
{
return $this->source === ConditionSource::CustomField;
}
}
42 changes: 36 additions & 6 deletions src/Data/VisibilityData.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Relaticle\CustomFields\Data;

use Illuminate\Database\Eloquent\Model;
use Relaticle\CustomFields\Enums\ConditionSource;
use Relaticle\CustomFields\Enums\VisibilityLogic;
use Relaticle\CustomFields\Enums\VisibilityMode;
use Spatie\LaravelData\Attributes\DataCollectionOf;
Expand Down Expand Up @@ -34,7 +36,7 @@ public function requiresConditions(): bool
/**
* @param array<string, mixed> $fieldValues
*/
public function evaluate(array $fieldValues): bool
public function evaluate(array $fieldValues, ?Model $record = null): bool
{
if (! $this->requiresConditions() || ! $this->conditions instanceof DataCollection) {
return $this->mode === VisibilityMode::ALWAYS_VISIBLE;
Expand All @@ -43,7 +45,7 @@ public function evaluate(array $fieldValues): bool
$results = [];

foreach ($this->conditions as $condition) {
$result = $this->evaluateCondition($condition, $fieldValues);
$result = $this->evaluateCondition($condition, $fieldValues, $record);
$results[] = $result;
}

Expand All @@ -55,14 +57,22 @@ public function evaluate(array $fieldValues): bool
/**
* @param array<string, mixed> $fieldValues
*/
private function evaluateCondition(VisibilityConditionData $condition, array $fieldValues): bool
{
$fieldValue = $fieldValues[$condition->field_code] ?? null;
private function evaluateCondition(
VisibilityConditionData $condition,
array $fieldValues,
?Model $record = null
): bool {
$fieldValue = match ($condition->source) {
ConditionSource::CustomField => $fieldValues[$condition->field_code] ?? null,
ConditionSource::ModelAttribute => $record?->getAttribute($condition->field_code),
};

return $condition->operator->evaluate($fieldValue, $condition->value);
}

/**
* Get dependent custom field codes (excludes model attribute conditions).
*
* @return array<int, string>
*/
public function getDependentFields(): array
Expand All @@ -74,9 +84,29 @@ public function getDependentFields(): array
$fields = [];

foreach ($this->conditions as $condition) {
$fields[] = $condition->field_code;
if ($condition->isCustomField()) {
$fields[] = $condition->field_code;
}
}

return array_unique($fields);
}

/**
* Check if any conditions reference model attributes.
*/
public function hasModelAttributeConditions(): bool
{
if (! $this->conditions instanceof DataCollection) {
return false;
}

foreach ($this->conditions as $condition) {
if ($condition->isModelAttribute()) {
return true;
}
}

return false;
}
}
21 changes: 21 additions & 0 deletions src/Enums/ConditionSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Relaticle\CustomFields\Enums;

use Filament\Support\Contracts\HasLabel;

enum ConditionSource: string implements HasLabel
{
case CustomField = 'custom_field';
case ModelAttribute = 'model_attribute';

public function getLabel(): string
{
return match ($this) {
self::CustomField => 'Custom Field',
self::ModelAttribute => 'Model Attribute',
};
}
}
6 changes: 5 additions & 1 deletion src/Enums/CustomFieldsFeature.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ enum CustomFieldsFeature: string
case FIELD_UNIQUE_VALUE = 'field_unique_value';
case FIELD_VALIDATION_RULES = 'field_validation_rules';

// Visibility features
case MODEL_ATTRIBUTE_CONDITIONS = 'model_attribute_conditions';
case SECTION_CONDITIONAL_VISIBILITY = 'section_conditional_visibility';

// Table/UI integration features
case UI_TABLE_COLUMNS = 'ui_table_columns';
case UI_TABLE_FILTERS = 'ui_table_filters';
Expand All @@ -29,5 +33,5 @@ enum CustomFieldsFeature: string
// System-level features
case SYSTEM_MANAGEMENT_INTERFACE = 'system_management_interface';
case SYSTEM_MULTI_TENANCY = 'system_multi_tenancy';
case SYSTEM_SECTIONS_ENABLED = 'system_sections_enabled';
case SYSTEM_SECTIONS = 'system_sections';
}
15 changes: 11 additions & 4 deletions src/Filament/Integration/Base/AbstractFormComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
public function __construct(
protected ValidationService $validationService,
protected CoreVisibilityLogicService $coreVisibilityLogic,
protected FrontendVisibilityService $frontendVisibilityService
protected FrontendVisibilityService $frontendVisibilityService,
) {}

/**
Expand Down Expand Up @@ -141,9 +141,16 @@ private function applyVisibility(
$allFields
);

return in_array($jsExpression, [null, '', '0'], true)
? $field
: $field->live()->visibleJs($jsExpression);
if (in_array($jsExpression, [null, '', '0'], true)) {
return $field;
}

// visibleJs alone handles both initial state (via x-cloak) and reactivity.
// Do NOT combine with visible() — server-side visible(false) prevents the
// component from rendering entirely, which blocks visibleJs from ever executing.
$field->live()->visibleJs($jsExpression);

return $field;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/Filament/Integration/Builders/BaseBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function forModel(Model|string $model): static
$this->explicitModel = $model;

// Only initialize sections query when sections are enabled
if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS_ENABLED)) {
if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS)) {
$this->sections = CustomFields::newSectionModel()->query()
->forEntityType($model::class)
->orderBy('sort_order');
Expand Down Expand Up @@ -141,7 +141,7 @@ protected function getFieldsDirectly(): Collection
*/
protected function getAllFields(): Collection
{
if (! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS_ENABLED)) {
if (! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS)) {
return $this->getFieldsDirectly();
}

Expand Down
11 changes: 8 additions & 3 deletions src/Filament/Integration/Builders/FormBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
namespace Relaticle\CustomFields\Filament\Integration\Builders;

use Filament\Schemas\Components\Grid;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Relaticle\CustomFields\Enums\CustomFieldsFeature;
use Relaticle\CustomFields\FeatureSystem\FeatureManager;
Expand Down Expand Up @@ -73,18 +74,22 @@ public function values(): Collection
);

// Return flat fields if sections are disabled or withoutSections is set
$sectionsDisabled = ! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS_ENABLED);
$sectionsDisabled = ! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS);
if ($this->withoutSections || $sectionsDisabled) {
return $allFields->map($createField);
}

$record = $this->explicitModel instanceof Model && $this->explicitModel->exists
? $this->explicitModel
: null;

return $this->getFilteredSections()
->map(function (CustomFieldSection $section) use ($sectionComponentFactory, $createField) {
->map(function (CustomFieldSection $section) use ($sectionComponentFactory, $createField, $allFields, $record) {
$fields = $section->fields->map($createField);

return $fields->isEmpty()
? null
: $sectionComponentFactory->create($section)->schema($fields->toArray());
: $sectionComponentFactory->create($section, $allFields, $record)->schema($fields->toArray());
})
->filter();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Filament/Integration/Builders/FormContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private function generateSchema(): array

// Use explicit setting if provided, otherwise check feature flag
$withoutSections = $this->withoutSections
?? ! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS_ENABLED);
?? ! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS);

$builder = app(FormBuilder::class);

Expand Down
2 changes: 1 addition & 1 deletion src/Filament/Integration/Builders/InfolistBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function values(null|(Model&HasCustomFields) $model = null): Collection
->when($this->visibleWhenFilled, fn (Entry $field): Entry => $field->visible(fn (mixed $state): bool => filled($state)));

// Check if sections are disabled
$sectionsDisabled = ! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS_ENABLED);
$sectionsDisabled = ! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS);

// When sections disabled, get fields directly without section context
if ($this->withoutSections || $sectionsDisabled) {
Expand Down
2 changes: 1 addition & 1 deletion src/Filament/Integration/Builders/InfolistContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private function generateSchema(): array

// Use explicit setting if provided, otherwise check feature flag
$withoutSections = $this->withoutSections
?? ! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS_ENABLED);
?? ! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS);

$builder = app(InfolistBuilder::class)
->forModel($model)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function __construct(
private Closure $closure,
ValidationService $validationService,
CoreVisibilityLogicService $coreVisibilityLogic,
FrontendVisibilityService $frontendVisibilityService
FrontendVisibilityService $frontendVisibilityService,
) {
parent::__construct($validationService, $coreVisibilityLogic, $frontendVisibilityService);
}
Expand Down
Loading